desicon-seal 1.0.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- desicon_seal-1.0.2/PKG-INFO +187 -0
- desicon_seal-1.0.2/README.md +164 -0
- desicon_seal-1.0.2/desicon_seal.egg-info/PKG-INFO +187 -0
- desicon_seal-1.0.2/desicon_seal.egg-info/SOURCES.txt +10 -0
- desicon_seal-1.0.2/desicon_seal.egg-info/dependency_links.txt +1 -0
- desicon_seal-1.0.2/desicon_seal.egg-info/requires.txt +1 -0
- desicon_seal-1.0.2/desicon_seal.egg-info/top_level.txt +1 -0
- desicon_seal-1.0.2/pyproject.toml +3 -0
- desicon_seal-1.0.2/seal/__init__.py +3 -0
- desicon_seal-1.0.2/seal/client.py +318 -0
- desicon_seal-1.0.2/setup.cfg +4 -0
- desicon_seal-1.0.2/setup.py +25 -0
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: desicon-seal
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Zero-latency App-Layer WAF, Automated SRE Logging, and AI Rescue Engine.
|
|
5
|
+
Home-page: https://github.com/Desicon-AI/seal-python
|
|
6
|
+
Author: Seal Enterprise Security
|
|
7
|
+
Author-email: hello@circle-sure.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.1
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Seal Python SDK Setup Documentation
|
|
25
|
+
|
|
26
|
+
## 1. Quick Start
|
|
27
|
+
|
|
28
|
+
### Step 1: Install the SDK
|
|
29
|
+
```bash
|
|
30
|
+
pip install desicon-seal
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 2: Initialize Seal & Add ASGI Middleware
|
|
34
|
+
For modern Python frameworks (FastAPI, Starlette, Django 3.0+), you can easily add the Edge-equivalent WAF to your application using the `SealASGIMiddleware`.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from fastapi import FastAPI
|
|
38
|
+
from seal import seal
|
|
39
|
+
|
|
40
|
+
app = FastAPI()
|
|
41
|
+
|
|
42
|
+
# 1. Initialize Telemetry & SRE
|
|
43
|
+
seal.init(
|
|
44
|
+
api_key="grp_live_xxxx", # From your dashboard
|
|
45
|
+
app_name="My FastAPI App",
|
|
46
|
+
environment="production"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# 2. Add the WAF Middleware to protect your API
|
|
50
|
+
app.add_middleware(seal.asgi_middleware())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 2. Configuration Options
|
|
54
|
+
| Python Parameter | Node.js / Browser Parameter | Type | Description |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| `api_key` | `apiKey` | `string` | Your project API key from the dashboard. |
|
|
57
|
+
| `app_name` | `appName` | `string` | A human-readable name for your application. |
|
|
58
|
+
| `environment` | `environment` | `string` | Current deployment environment: 'production', 'staging', or 'development'. |
|
|
59
|
+
| `rescue_engine` | `rescueEngine` | `boolean` | Set to true to enable automated AI hotfix generation and patching. |
|
|
60
|
+
| `level` | `level` | `string` | fatal | error | warning |
|
|
61
|
+
|
|
62
|
+
## 3. The Sensitivity Dialer
|
|
63
|
+
The Sensitivity Dialer controls how much noise Grumpy generates. Set it in two places:
|
|
64
|
+
1. **SDK**: Set level in grumpy.init().
|
|
65
|
+
2. **Dashboard**: Override per-project in Settings. Server enforces it.
|
|
66
|
+
|
|
67
|
+
### Levels:
|
|
68
|
+
- **fatal** (Low): Only unhandled crashes.
|
|
69
|
+
```js
|
|
70
|
+
grumpy.init({ level: "fatal" });
|
|
71
|
+
```
|
|
72
|
+
- **error** (Medium): Caught exceptions + fatal.
|
|
73
|
+
```js
|
|
74
|
+
grumpy.init({ level: "error" });
|
|
75
|
+
```
|
|
76
|
+
- **warning** (High): Everything. Good luck.
|
|
77
|
+
```js
|
|
78
|
+
grumpy.init({ level: "warning" });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 4. Grumpy Rescue Engine
|
|
82
|
+
When the Rescue Engine is enabled (via Project Settings), Grumpy doesn't just send you an alert. It instructs your connected AI to generate a functional, sandboxed JavaScript fix that addresses the logical error. The fix is minified and encrypted before leaving the server—only your SDK (holding the same API key) can decrypt and execute it.
|
|
83
|
+
|
|
84
|
+
Each fix is uniquely identified (grp_fix_xxxx) and location-aware: the same error type at two different code locations gets two separate fixes. If a fix fails to apply, the SDK automatically reports the failure, the bad fix is scrubbed server-side, and a fresh fix is generated on the next occurrence. No stale fixes, no fix-the-fix loops.
|
|
85
|
+
|
|
86
|
+
- 1. Enable 'Grumpy Rescue Engine' in your Project settings.
|
|
87
|
+
- 2. Ensure you have an AI Provider configured in settings.
|
|
88
|
+
- 3. When an unhandled error occurs, Grumpy generates an encrypted rescue fix.
|
|
89
|
+
- 4. The SDK decrypts and applies this fix in memory—invisible to network inspectors, DevTools, and MITM proxies.
|
|
90
|
+
|
|
91
|
+
### Stack Compatibility & Frameworks
|
|
92
|
+
The Rescue Engine dynamically patches JavaScript bugs in the browser, but it is fully compatible with ANY backend stack.
|
|
93
|
+
|
|
94
|
+
**Server-Rendered Apps:** Even if your backend is Python or PHP, your users still interact with HTML/JS in the browser. Drop the Grumpy JS SDK into your Jinja2, Django, or Blade templates. The Rescue Engine will catch and hotfix frontend JavaScript bugs seamlessly.
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<!DOCTYPE html>
|
|
98
|
+
<html>
|
|
99
|
+
<head>
|
|
100
|
+
<!-- Drop Grumpy before your other scripts -->
|
|
101
|
+
<script src="https://cdn.jsdelivr.net/gh/Desicon-AI/grumpy-cdn@main/grumpy.js"></script>
|
|
102
|
+
<script>
|
|
103
|
+
Grumpy.init({
|
|
104
|
+
apiKey: "{{ GRUMPY_API_KEY }}", // Passed from backend
|
|
105
|
+
appName: "My Web App",
|
|
106
|
+
rescueEngine: true
|
|
107
|
+
});
|
|
108
|
+
</script>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
{% block content %}{% endblock %}
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 5. AI Personas
|
|
117
|
+
Grumpy speaks in three voices. Choose the one that fits your team culture. Personas are set at the Organization level and apply to all projects.
|
|
118
|
+
|
|
119
|
+
### Grumpy (Sarcastic & Mean) — All Plans
|
|
120
|
+
The default. Grumpy roasts your code, insults your variable names, and grudgingly tells you how to fix it. Perfect for teams with thick skin and a sense of humor.
|
|
121
|
+
|
|
122
|
+
> Example: "You passed a string to parseInt and expected magic? Cast your variables, you absolute menace. Here's the fix, don't make me say it twice."
|
|
123
|
+
|
|
124
|
+
### Friendly (Supportive & Positive) — Pro / Team
|
|
125
|
+
A warm, encouraging senior dev who wants you to know it's going to be okay. Uses emojis. Celebrates small wins. Great for junior-heavy teams or if HR keeps filing complaints.
|
|
126
|
+
|
|
127
|
+
> Example: "Hey! 👋 Looks like a small type mismatch slipped through — happens to everyone! Here's a quick fix. You're doing great! 🎉"
|
|
128
|
+
|
|
129
|
+
### Professional (Strategic & Objective) — Pro / Team
|
|
130
|
+
An enterprise-grade SRE delivering strictly factual root cause analysis and remediation steps. No jokes, no fluff. Designed for large organizations, regulated industries, and teams that need clean audit trails.
|
|
131
|
+
|
|
132
|
+
> Example: "Root Cause: Type coercion failure in paymentAPI.submit(). Remediation: Validate input type before invocation. Patch recommended."
|
|
133
|
+
|
|
134
|
+
### How to configure
|
|
135
|
+
All future error analyses across every project in your organization will use the new voice.
|
|
136
|
+
```text
|
|
137
|
+
1. Dashboard → Settings → Organization Persona
|
|
138
|
+
2. Select your preferred persona and click Save
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 7. Integrations
|
|
142
|
+
|
|
143
|
+
### Bring Your Own AI (BYOAI)
|
|
144
|
+
Grumpy works out of the box for free using our massive database of hand-crafted roasts. But if you want AI to suggest actual code fixes, connect any AI provider (OpenAI, Anthropic, Groq, Ollama). We never markup token usage. If your API goes down, Grumpy seamlessly falls back to standard mode.
|
|
145
|
+
```text
|
|
146
|
+
Dashboard → Settings → Select Provider → Paste Key
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Slack Webhooks
|
|
150
|
+
Grumpy sends rich, formatted alerts directly to your Slack channels with full stack traces, AI analysis, and severity badges.
|
|
151
|
+
```text
|
|
152
|
+
1. Dashboard → Settings → Connect Slack
|
|
153
|
+
2. Slack → Channel → Settings → Integrations → Add Grumpy
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Discord Webhooks
|
|
157
|
+
Same rich alerts, same brutal honesty — delivered to your Discord server. Perfect for indie devs and open source projects.
|
|
158
|
+
```text
|
|
159
|
+
Dashboard → Settings → Connect Discord (automatic)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 8. The 'Not My Fault' Engine
|
|
163
|
+
Our 'Not My Fault' engine analyzes stack traces. If a crash originated from an external package, we will notify the maintainer of the issue so they can work on it. But we are smart about it. If we notice that the error was caused by missing parameters or incorrect usage by the developer, rather than sending the maintainer a ticket, we notify the developer of the correct use.
|
|
164
|
+
|
|
165
|
+
### For Maintainers
|
|
166
|
+
Sign up for a free Grumpy account to claim and track your public npm/pip packages. You only need a paid subscription if you want to use Grumpy to monitor your own applications.
|
|
167
|
+
|
|
168
|
+
### For Developers
|
|
169
|
+
Enable this feature within your project settings so that genuine errors from external packages are automatically sent to the maintainers.
|
|
170
|
+
```text
|
|
171
|
+
Dashboard → Settings → Enable 'Not My Fault'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 9. Sandbox Testing
|
|
175
|
+
Use your Sandbox project to test Grumpy's ingestion and AI analysis without triggering webhooks or skewing your live analytics. Sandbox API keys always start with sandbox_ and can be found in your Dashboard. You can also test everything interactively in the Playground.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from grumpy_ai import grumpy
|
|
179
|
+
|
|
180
|
+
grumpy.init(
|
|
181
|
+
api_key="sandbox_YOUR_KEY",
|
|
182
|
+
app_name="Test App",
|
|
183
|
+
environment="development",
|
|
184
|
+
endpoint="https://sealengine.desicon.ai/api/v1/sandbox/ingest"
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Seal Python SDK Setup Documentation
|
|
2
|
+
|
|
3
|
+
## 1. Quick Start
|
|
4
|
+
|
|
5
|
+
### Step 1: Install the SDK
|
|
6
|
+
```bash
|
|
7
|
+
pip install desicon-seal
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
### Step 2: Initialize Seal & Add ASGI Middleware
|
|
11
|
+
For modern Python frameworks (FastAPI, Starlette, Django 3.0+), you can easily add the Edge-equivalent WAF to your application using the `SealASGIMiddleware`.
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from fastapi import FastAPI
|
|
15
|
+
from seal import seal
|
|
16
|
+
|
|
17
|
+
app = FastAPI()
|
|
18
|
+
|
|
19
|
+
# 1. Initialize Telemetry & SRE
|
|
20
|
+
seal.init(
|
|
21
|
+
api_key="grp_live_xxxx", # From your dashboard
|
|
22
|
+
app_name="My FastAPI App",
|
|
23
|
+
environment="production"
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
# 2. Add the WAF Middleware to protect your API
|
|
27
|
+
app.add_middleware(seal.asgi_middleware())
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## 2. Configuration Options
|
|
31
|
+
| Python Parameter | Node.js / Browser Parameter | Type | Description |
|
|
32
|
+
| --- | --- | --- | --- |
|
|
33
|
+
| `api_key` | `apiKey` | `string` | Your project API key from the dashboard. |
|
|
34
|
+
| `app_name` | `appName` | `string` | A human-readable name for your application. |
|
|
35
|
+
| `environment` | `environment` | `string` | Current deployment environment: 'production', 'staging', or 'development'. |
|
|
36
|
+
| `rescue_engine` | `rescueEngine` | `boolean` | Set to true to enable automated AI hotfix generation and patching. |
|
|
37
|
+
| `level` | `level` | `string` | fatal | error | warning |
|
|
38
|
+
|
|
39
|
+
## 3. The Sensitivity Dialer
|
|
40
|
+
The Sensitivity Dialer controls how much noise Grumpy generates. Set it in two places:
|
|
41
|
+
1. **SDK**: Set level in grumpy.init().
|
|
42
|
+
2. **Dashboard**: Override per-project in Settings. Server enforces it.
|
|
43
|
+
|
|
44
|
+
### Levels:
|
|
45
|
+
- **fatal** (Low): Only unhandled crashes.
|
|
46
|
+
```js
|
|
47
|
+
grumpy.init({ level: "fatal" });
|
|
48
|
+
```
|
|
49
|
+
- **error** (Medium): Caught exceptions + fatal.
|
|
50
|
+
```js
|
|
51
|
+
grumpy.init({ level: "error" });
|
|
52
|
+
```
|
|
53
|
+
- **warning** (High): Everything. Good luck.
|
|
54
|
+
```js
|
|
55
|
+
grumpy.init({ level: "warning" });
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## 4. Grumpy Rescue Engine
|
|
59
|
+
When the Rescue Engine is enabled (via Project Settings), Grumpy doesn't just send you an alert. It instructs your connected AI to generate a functional, sandboxed JavaScript fix that addresses the logical error. The fix is minified and encrypted before leaving the server—only your SDK (holding the same API key) can decrypt and execute it.
|
|
60
|
+
|
|
61
|
+
Each fix is uniquely identified (grp_fix_xxxx) and location-aware: the same error type at two different code locations gets two separate fixes. If a fix fails to apply, the SDK automatically reports the failure, the bad fix is scrubbed server-side, and a fresh fix is generated on the next occurrence. No stale fixes, no fix-the-fix loops.
|
|
62
|
+
|
|
63
|
+
- 1. Enable 'Grumpy Rescue Engine' in your Project settings.
|
|
64
|
+
- 2. Ensure you have an AI Provider configured in settings.
|
|
65
|
+
- 3. When an unhandled error occurs, Grumpy generates an encrypted rescue fix.
|
|
66
|
+
- 4. The SDK decrypts and applies this fix in memory—invisible to network inspectors, DevTools, and MITM proxies.
|
|
67
|
+
|
|
68
|
+
### Stack Compatibility & Frameworks
|
|
69
|
+
The Rescue Engine dynamically patches JavaScript bugs in the browser, but it is fully compatible with ANY backend stack.
|
|
70
|
+
|
|
71
|
+
**Server-Rendered Apps:** Even if your backend is Python or PHP, your users still interact with HTML/JS in the browser. Drop the Grumpy JS SDK into your Jinja2, Django, or Blade templates. The Rescue Engine will catch and hotfix frontend JavaScript bugs seamlessly.
|
|
72
|
+
|
|
73
|
+
```html
|
|
74
|
+
<!DOCTYPE html>
|
|
75
|
+
<html>
|
|
76
|
+
<head>
|
|
77
|
+
<!-- Drop Grumpy before your other scripts -->
|
|
78
|
+
<script src="https://cdn.jsdelivr.net/gh/Desicon-AI/grumpy-cdn@main/grumpy.js"></script>
|
|
79
|
+
<script>
|
|
80
|
+
Grumpy.init({
|
|
81
|
+
apiKey: "{{ GRUMPY_API_KEY }}", // Passed from backend
|
|
82
|
+
appName: "My Web App",
|
|
83
|
+
rescueEngine: true
|
|
84
|
+
});
|
|
85
|
+
</script>
|
|
86
|
+
</head>
|
|
87
|
+
<body>
|
|
88
|
+
{% block content %}{% endblock %}
|
|
89
|
+
</body>
|
|
90
|
+
</html>
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
## 5. AI Personas
|
|
94
|
+
Grumpy speaks in three voices. Choose the one that fits your team culture. Personas are set at the Organization level and apply to all projects.
|
|
95
|
+
|
|
96
|
+
### Grumpy (Sarcastic & Mean) — All Plans
|
|
97
|
+
The default. Grumpy roasts your code, insults your variable names, and grudgingly tells you how to fix it. Perfect for teams with thick skin and a sense of humor.
|
|
98
|
+
|
|
99
|
+
> Example: "You passed a string to parseInt and expected magic? Cast your variables, you absolute menace. Here's the fix, don't make me say it twice."
|
|
100
|
+
|
|
101
|
+
### Friendly (Supportive & Positive) — Pro / Team
|
|
102
|
+
A warm, encouraging senior dev who wants you to know it's going to be okay. Uses emojis. Celebrates small wins. Great for junior-heavy teams or if HR keeps filing complaints.
|
|
103
|
+
|
|
104
|
+
> Example: "Hey! 👋 Looks like a small type mismatch slipped through — happens to everyone! Here's a quick fix. You're doing great! 🎉"
|
|
105
|
+
|
|
106
|
+
### Professional (Strategic & Objective) — Pro / Team
|
|
107
|
+
An enterprise-grade SRE delivering strictly factual root cause analysis and remediation steps. No jokes, no fluff. Designed for large organizations, regulated industries, and teams that need clean audit trails.
|
|
108
|
+
|
|
109
|
+
> Example: "Root Cause: Type coercion failure in paymentAPI.submit(). Remediation: Validate input type before invocation. Patch recommended."
|
|
110
|
+
|
|
111
|
+
### How to configure
|
|
112
|
+
All future error analyses across every project in your organization will use the new voice.
|
|
113
|
+
```text
|
|
114
|
+
1. Dashboard → Settings → Organization Persona
|
|
115
|
+
2. Select your preferred persona and click Save
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## 7. Integrations
|
|
119
|
+
|
|
120
|
+
### Bring Your Own AI (BYOAI)
|
|
121
|
+
Grumpy works out of the box for free using our massive database of hand-crafted roasts. But if you want AI to suggest actual code fixes, connect any AI provider (OpenAI, Anthropic, Groq, Ollama). We never markup token usage. If your API goes down, Grumpy seamlessly falls back to standard mode.
|
|
122
|
+
```text
|
|
123
|
+
Dashboard → Settings → Select Provider → Paste Key
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Slack Webhooks
|
|
127
|
+
Grumpy sends rich, formatted alerts directly to your Slack channels with full stack traces, AI analysis, and severity badges.
|
|
128
|
+
```text
|
|
129
|
+
1. Dashboard → Settings → Connect Slack
|
|
130
|
+
2. Slack → Channel → Settings → Integrations → Add Grumpy
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Discord Webhooks
|
|
134
|
+
Same rich alerts, same brutal honesty — delivered to your Discord server. Perfect for indie devs and open source projects.
|
|
135
|
+
```text
|
|
136
|
+
Dashboard → Settings → Connect Discord (automatic)
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 8. The 'Not My Fault' Engine
|
|
140
|
+
Our 'Not My Fault' engine analyzes stack traces. If a crash originated from an external package, we will notify the maintainer of the issue so they can work on it. But we are smart about it. If we notice that the error was caused by missing parameters or incorrect usage by the developer, rather than sending the maintainer a ticket, we notify the developer of the correct use.
|
|
141
|
+
|
|
142
|
+
### For Maintainers
|
|
143
|
+
Sign up for a free Grumpy account to claim and track your public npm/pip packages. You only need a paid subscription if you want to use Grumpy to monitor your own applications.
|
|
144
|
+
|
|
145
|
+
### For Developers
|
|
146
|
+
Enable this feature within your project settings so that genuine errors from external packages are automatically sent to the maintainers.
|
|
147
|
+
```text
|
|
148
|
+
Dashboard → Settings → Enable 'Not My Fault'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## 9. Sandbox Testing
|
|
152
|
+
Use your Sandbox project to test Grumpy's ingestion and AI analysis without triggering webhooks or skewing your live analytics. Sandbox API keys always start with sandbox_ and can be found in your Dashboard. You can also test everything interactively in the Playground.
|
|
153
|
+
|
|
154
|
+
```python
|
|
155
|
+
from grumpy_ai import grumpy
|
|
156
|
+
|
|
157
|
+
grumpy.init(
|
|
158
|
+
api_key="sandbox_YOUR_KEY",
|
|
159
|
+
app_name="Test App",
|
|
160
|
+
environment="development",
|
|
161
|
+
endpoint="https://sealengine.desicon.ai/api/v1/sandbox/ingest"
|
|
162
|
+
)
|
|
163
|
+
```
|
|
164
|
+
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: desicon-seal
|
|
3
|
+
Version: 1.0.2
|
|
4
|
+
Summary: Zero-latency App-Layer WAF, Automated SRE Logging, and AI Rescue Engine.
|
|
5
|
+
Home-page: https://github.com/Desicon-AI/seal-python
|
|
6
|
+
Author: Seal Enterprise Security
|
|
7
|
+
Author-email: hello@circle-sure.com
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.7
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
Requires-Dist: requests>=2.25.1
|
|
14
|
+
Dynamic: author
|
|
15
|
+
Dynamic: author-email
|
|
16
|
+
Dynamic: classifier
|
|
17
|
+
Dynamic: description
|
|
18
|
+
Dynamic: description-content-type
|
|
19
|
+
Dynamic: home-page
|
|
20
|
+
Dynamic: requires-dist
|
|
21
|
+
Dynamic: requires-python
|
|
22
|
+
Dynamic: summary
|
|
23
|
+
|
|
24
|
+
# Seal Python SDK Setup Documentation
|
|
25
|
+
|
|
26
|
+
## 1. Quick Start
|
|
27
|
+
|
|
28
|
+
### Step 1: Install the SDK
|
|
29
|
+
```bash
|
|
30
|
+
pip install desicon-seal
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Step 2: Initialize Seal & Add ASGI Middleware
|
|
34
|
+
For modern Python frameworks (FastAPI, Starlette, Django 3.0+), you can easily add the Edge-equivalent WAF to your application using the `SealASGIMiddleware`.
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
from fastapi import FastAPI
|
|
38
|
+
from seal import seal
|
|
39
|
+
|
|
40
|
+
app = FastAPI()
|
|
41
|
+
|
|
42
|
+
# 1. Initialize Telemetry & SRE
|
|
43
|
+
seal.init(
|
|
44
|
+
api_key="grp_live_xxxx", # From your dashboard
|
|
45
|
+
app_name="My FastAPI App",
|
|
46
|
+
environment="production"
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# 2. Add the WAF Middleware to protect your API
|
|
50
|
+
app.add_middleware(seal.asgi_middleware())
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 2. Configuration Options
|
|
54
|
+
| Python Parameter | Node.js / Browser Parameter | Type | Description |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| `api_key` | `apiKey` | `string` | Your project API key from the dashboard. |
|
|
57
|
+
| `app_name` | `appName` | `string` | A human-readable name for your application. |
|
|
58
|
+
| `environment` | `environment` | `string` | Current deployment environment: 'production', 'staging', or 'development'. |
|
|
59
|
+
| `rescue_engine` | `rescueEngine` | `boolean` | Set to true to enable automated AI hotfix generation and patching. |
|
|
60
|
+
| `level` | `level` | `string` | fatal | error | warning |
|
|
61
|
+
|
|
62
|
+
## 3. The Sensitivity Dialer
|
|
63
|
+
The Sensitivity Dialer controls how much noise Grumpy generates. Set it in two places:
|
|
64
|
+
1. **SDK**: Set level in grumpy.init().
|
|
65
|
+
2. **Dashboard**: Override per-project in Settings. Server enforces it.
|
|
66
|
+
|
|
67
|
+
### Levels:
|
|
68
|
+
- **fatal** (Low): Only unhandled crashes.
|
|
69
|
+
```js
|
|
70
|
+
grumpy.init({ level: "fatal" });
|
|
71
|
+
```
|
|
72
|
+
- **error** (Medium): Caught exceptions + fatal.
|
|
73
|
+
```js
|
|
74
|
+
grumpy.init({ level: "error" });
|
|
75
|
+
```
|
|
76
|
+
- **warning** (High): Everything. Good luck.
|
|
77
|
+
```js
|
|
78
|
+
grumpy.init({ level: "warning" });
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 4. Grumpy Rescue Engine
|
|
82
|
+
When the Rescue Engine is enabled (via Project Settings), Grumpy doesn't just send you an alert. It instructs your connected AI to generate a functional, sandboxed JavaScript fix that addresses the logical error. The fix is minified and encrypted before leaving the server—only your SDK (holding the same API key) can decrypt and execute it.
|
|
83
|
+
|
|
84
|
+
Each fix is uniquely identified (grp_fix_xxxx) and location-aware: the same error type at two different code locations gets two separate fixes. If a fix fails to apply, the SDK automatically reports the failure, the bad fix is scrubbed server-side, and a fresh fix is generated on the next occurrence. No stale fixes, no fix-the-fix loops.
|
|
85
|
+
|
|
86
|
+
- 1. Enable 'Grumpy Rescue Engine' in your Project settings.
|
|
87
|
+
- 2. Ensure you have an AI Provider configured in settings.
|
|
88
|
+
- 3. When an unhandled error occurs, Grumpy generates an encrypted rescue fix.
|
|
89
|
+
- 4. The SDK decrypts and applies this fix in memory—invisible to network inspectors, DevTools, and MITM proxies.
|
|
90
|
+
|
|
91
|
+
### Stack Compatibility & Frameworks
|
|
92
|
+
The Rescue Engine dynamically patches JavaScript bugs in the browser, but it is fully compatible with ANY backend stack.
|
|
93
|
+
|
|
94
|
+
**Server-Rendered Apps:** Even if your backend is Python or PHP, your users still interact with HTML/JS in the browser. Drop the Grumpy JS SDK into your Jinja2, Django, or Blade templates. The Rescue Engine will catch and hotfix frontend JavaScript bugs seamlessly.
|
|
95
|
+
|
|
96
|
+
```html
|
|
97
|
+
<!DOCTYPE html>
|
|
98
|
+
<html>
|
|
99
|
+
<head>
|
|
100
|
+
<!-- Drop Grumpy before your other scripts -->
|
|
101
|
+
<script src="https://cdn.jsdelivr.net/gh/Desicon-AI/grumpy-cdn@main/grumpy.js"></script>
|
|
102
|
+
<script>
|
|
103
|
+
Grumpy.init({
|
|
104
|
+
apiKey: "{{ GRUMPY_API_KEY }}", // Passed from backend
|
|
105
|
+
appName: "My Web App",
|
|
106
|
+
rescueEngine: true
|
|
107
|
+
});
|
|
108
|
+
</script>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
{% block content %}{% endblock %}
|
|
112
|
+
</body>
|
|
113
|
+
</html>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 5. AI Personas
|
|
117
|
+
Grumpy speaks in three voices. Choose the one that fits your team culture. Personas are set at the Organization level and apply to all projects.
|
|
118
|
+
|
|
119
|
+
### Grumpy (Sarcastic & Mean) — All Plans
|
|
120
|
+
The default. Grumpy roasts your code, insults your variable names, and grudgingly tells you how to fix it. Perfect for teams with thick skin and a sense of humor.
|
|
121
|
+
|
|
122
|
+
> Example: "You passed a string to parseInt and expected magic? Cast your variables, you absolute menace. Here's the fix, don't make me say it twice."
|
|
123
|
+
|
|
124
|
+
### Friendly (Supportive & Positive) — Pro / Team
|
|
125
|
+
A warm, encouraging senior dev who wants you to know it's going to be okay. Uses emojis. Celebrates small wins. Great for junior-heavy teams or if HR keeps filing complaints.
|
|
126
|
+
|
|
127
|
+
> Example: "Hey! 👋 Looks like a small type mismatch slipped through — happens to everyone! Here's a quick fix. You're doing great! 🎉"
|
|
128
|
+
|
|
129
|
+
### Professional (Strategic & Objective) — Pro / Team
|
|
130
|
+
An enterprise-grade SRE delivering strictly factual root cause analysis and remediation steps. No jokes, no fluff. Designed for large organizations, regulated industries, and teams that need clean audit trails.
|
|
131
|
+
|
|
132
|
+
> Example: "Root Cause: Type coercion failure in paymentAPI.submit(). Remediation: Validate input type before invocation. Patch recommended."
|
|
133
|
+
|
|
134
|
+
### How to configure
|
|
135
|
+
All future error analyses across every project in your organization will use the new voice.
|
|
136
|
+
```text
|
|
137
|
+
1. Dashboard → Settings → Organization Persona
|
|
138
|
+
2. Select your preferred persona and click Save
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## 7. Integrations
|
|
142
|
+
|
|
143
|
+
### Bring Your Own AI (BYOAI)
|
|
144
|
+
Grumpy works out of the box for free using our massive database of hand-crafted roasts. But if you want AI to suggest actual code fixes, connect any AI provider (OpenAI, Anthropic, Groq, Ollama). We never markup token usage. If your API goes down, Grumpy seamlessly falls back to standard mode.
|
|
145
|
+
```text
|
|
146
|
+
Dashboard → Settings → Select Provider → Paste Key
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Slack Webhooks
|
|
150
|
+
Grumpy sends rich, formatted alerts directly to your Slack channels with full stack traces, AI analysis, and severity badges.
|
|
151
|
+
```text
|
|
152
|
+
1. Dashboard → Settings → Connect Slack
|
|
153
|
+
2. Slack → Channel → Settings → Integrations → Add Grumpy
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### Discord Webhooks
|
|
157
|
+
Same rich alerts, same brutal honesty — delivered to your Discord server. Perfect for indie devs and open source projects.
|
|
158
|
+
```text
|
|
159
|
+
Dashboard → Settings → Connect Discord (automatic)
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## 8. The 'Not My Fault' Engine
|
|
163
|
+
Our 'Not My Fault' engine analyzes stack traces. If a crash originated from an external package, we will notify the maintainer of the issue so they can work on it. But we are smart about it. If we notice that the error was caused by missing parameters or incorrect usage by the developer, rather than sending the maintainer a ticket, we notify the developer of the correct use.
|
|
164
|
+
|
|
165
|
+
### For Maintainers
|
|
166
|
+
Sign up for a free Grumpy account to claim and track your public npm/pip packages. You only need a paid subscription if you want to use Grumpy to monitor your own applications.
|
|
167
|
+
|
|
168
|
+
### For Developers
|
|
169
|
+
Enable this feature within your project settings so that genuine errors from external packages are automatically sent to the maintainers.
|
|
170
|
+
```text
|
|
171
|
+
Dashboard → Settings → Enable 'Not My Fault'
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 9. Sandbox Testing
|
|
175
|
+
Use your Sandbox project to test Grumpy's ingestion and AI analysis without triggering webhooks or skewing your live analytics. Sandbox API keys always start with sandbox_ and can be found in your Dashboard. You can also test everything interactively in the Playground.
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from grumpy_ai import grumpy
|
|
179
|
+
|
|
180
|
+
grumpy.init(
|
|
181
|
+
api_key="sandbox_YOUR_KEY",
|
|
182
|
+
app_name="Test App",
|
|
183
|
+
environment="development",
|
|
184
|
+
endpoint="https://sealengine.desicon.ai/api/v1/sandbox/ingest"
|
|
185
|
+
)
|
|
186
|
+
```
|
|
187
|
+
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
setup.py
|
|
4
|
+
desicon_seal.egg-info/PKG-INFO
|
|
5
|
+
desicon_seal.egg-info/SOURCES.txt
|
|
6
|
+
desicon_seal.egg-info/dependency_links.txt
|
|
7
|
+
desicon_seal.egg-info/requires.txt
|
|
8
|
+
desicon_seal.egg-info/top_level.txt
|
|
9
|
+
seal/__init__.py
|
|
10
|
+
seal/client.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.25.1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
seal
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
import os
|
|
3
|
+
import traceback
|
|
4
|
+
import requests
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
import hmac
|
|
8
|
+
import hashlib
|
|
9
|
+
import json
|
|
10
|
+
|
|
11
|
+
class SealClient:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.api_key = None
|
|
14
|
+
self.signing_secret = None
|
|
15
|
+
self.environment = "development"
|
|
16
|
+
self.app_name = "unknown_app"
|
|
17
|
+
self._original_excepthook = None
|
|
18
|
+
self.ingest_url = "https://sealengine.desicon.ai/api/v1/ingest"
|
|
19
|
+
self.start_time = int(time.time() * 1000)
|
|
20
|
+
|
|
21
|
+
def init(self, api_key: str, app_name: str, environment: str = "production", ingest_url: str = None, waf: dict = None, signing_secret: str = None):
|
|
22
|
+
self.api_key = api_key
|
|
23
|
+
self.signing_secret = signing_secret
|
|
24
|
+
self.app_name = app_name
|
|
25
|
+
self.environment = environment
|
|
26
|
+
if ingest_url:
|
|
27
|
+
self.ingest_url = ingest_url
|
|
28
|
+
|
|
29
|
+
self.waf_config = {
|
|
30
|
+
"geoBlocking": { "blockedCountries": [], "action": "report" },
|
|
31
|
+
"maliciousScanners": { "action": "drop" },
|
|
32
|
+
"methodTampering": { "action": "report" },
|
|
33
|
+
"payloadOverflow": { "maxPayloadSize": 5242880, "action": "report" },
|
|
34
|
+
"pathTraversal": { "action": "drop" }
|
|
35
|
+
}
|
|
36
|
+
if waf:
|
|
37
|
+
# Deep update
|
|
38
|
+
for k, v in waf.items():
|
|
39
|
+
if k in self.waf_config and isinstance(v, dict):
|
|
40
|
+
self.waf_config[k].update(v)
|
|
41
|
+
else:
|
|
42
|
+
self.waf_config[k] = v
|
|
43
|
+
|
|
44
|
+
# Override the global exception hook
|
|
45
|
+
self._original_excepthook = sys.excepthook
|
|
46
|
+
sys.excepthook = self._seal_excepthook
|
|
47
|
+
|
|
48
|
+
# Ping the backend to auto-resolve old errors on deployment/startup
|
|
49
|
+
try:
|
|
50
|
+
import requests
|
|
51
|
+
headers = self._get_headers()
|
|
52
|
+
ping_url = self.ingest_url + "/ping" if not self.ingest_url.endswith("/") else self.ingest_url + "ping"
|
|
53
|
+
requests.post(ping_url, headers=headers, timeout=3)
|
|
54
|
+
|
|
55
|
+
# Start the Dead Man's Switch heartbeat
|
|
56
|
+
self._start_heartbeat()
|
|
57
|
+
except Exception as e:
|
|
58
|
+
# Silently fail so we don't break the host app if the network is down
|
|
59
|
+
pass
|
|
60
|
+
|
|
61
|
+
print(f"Seal.ai initialized for {self.app_name} ({self.environment}). We are watching you.")
|
|
62
|
+
|
|
63
|
+
def _get_headers(self, payload_str=None):
|
|
64
|
+
headers = {"X-API-Key": self.api_key}
|
|
65
|
+
if payload_str and self.signing_secret:
|
|
66
|
+
timestamp = str(int(time.time()))
|
|
67
|
+
msg = f"{timestamp}.{payload_str}".encode('utf-8')
|
|
68
|
+
signature = hmac.new(self.signing_secret.encode('utf-8'), msg, hashlib.sha256).hexdigest()
|
|
69
|
+
headers["X-Seal-Timestamp"] = timestamp
|
|
70
|
+
headers["X-Seal-Signature"] = signature
|
|
71
|
+
elif self.signing_secret:
|
|
72
|
+
timestamp = str(int(time.time()))
|
|
73
|
+
msg = f"{timestamp}.".encode('utf-8')
|
|
74
|
+
signature = hmac.new(self.signing_secret.encode('utf-8'), msg, hashlib.sha256).hexdigest()
|
|
75
|
+
headers["X-Seal-Timestamp"] = timestamp
|
|
76
|
+
headers["X-Seal-Signature"] = signature
|
|
77
|
+
return headers
|
|
78
|
+
|
|
79
|
+
def _start_heartbeat(self):
|
|
80
|
+
def heartbeat_loop():
|
|
81
|
+
payload = {
|
|
82
|
+
"app_name": self.app_name,
|
|
83
|
+
"environment": self.environment,
|
|
84
|
+
"started_at": self.start_time
|
|
85
|
+
}
|
|
86
|
+
headers = self._get_headers(json.dumps(payload))
|
|
87
|
+
|
|
88
|
+
heartbeat_url = self.ingest_url + "/heartbeat" if not self.ingest_url.endswith("/") else self.ingest_url + "heartbeat"
|
|
89
|
+
|
|
90
|
+
# Initial ping
|
|
91
|
+
try:
|
|
92
|
+
requests.post(heartbeat_url, json=payload, headers=headers, timeout=5)
|
|
93
|
+
except Exception:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
while True:
|
|
97
|
+
time.sleep(60)
|
|
98
|
+
try:
|
|
99
|
+
requests.post(heartbeat_url, json=payload, headers=headers, timeout=5)
|
|
100
|
+
except Exception:
|
|
101
|
+
pass # Silently fail
|
|
102
|
+
|
|
103
|
+
thread = threading.Thread(target=heartbeat_loop, daemon=True)
|
|
104
|
+
thread.start()
|
|
105
|
+
|
|
106
|
+
def _extract_code_context(self, tb):
|
|
107
|
+
"""Walks the stack trace to find the last file and extracts the surrounding lines."""
|
|
108
|
+
try:
|
|
109
|
+
# Extract the raw traceback
|
|
110
|
+
extracted = traceback.extract_tb(tb)
|
|
111
|
+
if not extracted:
|
|
112
|
+
return "No traceback available."
|
|
113
|
+
|
|
114
|
+
# Find the last frame that is actually in our project code
|
|
115
|
+
last_frame = extracted[-1]
|
|
116
|
+
filename = last_frame.filename
|
|
117
|
+
lineno = last_frame.lineno
|
|
118
|
+
|
|
119
|
+
if not os.path.exists(filename):
|
|
120
|
+
return f"Could not locate {filename} on disk."
|
|
121
|
+
|
|
122
|
+
with open(filename, 'r', encoding='utf-8') as f:
|
|
123
|
+
lines = f.readlines()
|
|
124
|
+
|
|
125
|
+
# Grab 5 lines before and 5 lines after the error
|
|
126
|
+
start_idx = max(0, lineno - 6)
|
|
127
|
+
end_idx = min(len(lines), lineno + 5)
|
|
128
|
+
|
|
129
|
+
context = ""
|
|
130
|
+
for i in range(start_idx, end_idx):
|
|
131
|
+
prefix = ">> " if i == (lineno - 1) else " "
|
|
132
|
+
context += f"{prefix}{i + 1}: {lines[i]}"
|
|
133
|
+
|
|
134
|
+
return context
|
|
135
|
+
except Exception as e:
|
|
136
|
+
return f"Failed to extract context: {str(e)}"
|
|
137
|
+
|
|
138
|
+
def _seal_excepthook(self, exc_type, exc_value, exc_traceback):
|
|
139
|
+
tb_str = "".join(traceback.format_exception(exc_type, exc_value, exc_traceback))
|
|
140
|
+
context_str = self._extract_code_context(exc_traceback)
|
|
141
|
+
|
|
142
|
+
payload = {
|
|
143
|
+
"app_name": self.app_name,
|
|
144
|
+
"error_type": exc_type.__name__,
|
|
145
|
+
"error_message": str(exc_value),
|
|
146
|
+
"stack_trace": tb_str,
|
|
147
|
+
"code_context": context_str,
|
|
148
|
+
"environment": self.environment
|
|
149
|
+
}
|
|
150
|
+
headers = self._get_headers(json.dumps(payload))
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
print(f"\n[Seal.ai] Catching {exc_type.__name__}... shipping to SRE engine.")
|
|
154
|
+
resp = requests.post(self.ingest_url, json=payload, headers=headers, timeout=5)
|
|
155
|
+
if resp.status_code == 200:
|
|
156
|
+
data = resp.json()
|
|
157
|
+
if data.get("status") == "deduplicated":
|
|
158
|
+
print(f"[Seal.ai] Deduped (seen {data.get('count')} times).")
|
|
159
|
+
else:
|
|
160
|
+
print(f"\n🔔 SEAL'S ANALYSIS:\n{data.get('analysis')}\n")
|
|
161
|
+
except Exception as e:
|
|
162
|
+
print(f"[Seal.ai] Failed to contact server: {e}")
|
|
163
|
+
|
|
164
|
+
# Let it crash normally so we don't break the actual application
|
|
165
|
+
if self._original_excepthook:
|
|
166
|
+
self._original_excepthook(exc_type, exc_value, exc_traceback)
|
|
167
|
+
|
|
168
|
+
def _report_threat(self, threat_type, ip, scope, details=None):
|
|
169
|
+
if not self.api_key:
|
|
170
|
+
return
|
|
171
|
+
|
|
172
|
+
def _send():
|
|
173
|
+
headers = dict(scope.get("headers", []))
|
|
174
|
+
headers_str = {k.decode("utf-8", "ignore"): v.decode("utf-8", "ignore") for k, v in headers.items()}
|
|
175
|
+
|
|
176
|
+
payload = {
|
|
177
|
+
"app_name": self.app_name,
|
|
178
|
+
"environment": self.environment,
|
|
179
|
+
"ip_address": ip,
|
|
180
|
+
"threat_type": threat_type,
|
|
181
|
+
"context": {
|
|
182
|
+
"method": scope.get("method", ""),
|
|
183
|
+
"url": scope.get("path", ""),
|
|
184
|
+
"headers": headers_str,
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if details:
|
|
188
|
+
payload["context"].update(details)
|
|
189
|
+
|
|
190
|
+
threat_url = self.ingest_url + "/threat" if not self.ingest_url.endswith("/") else self.ingest_url + "threat"
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
import requests
|
|
194
|
+
headers = self._get_headers(json.dumps(payload))
|
|
195
|
+
requests.post(threat_url, json=payload, headers=headers, timeout=3)
|
|
196
|
+
except Exception:
|
|
197
|
+
pass
|
|
198
|
+
|
|
199
|
+
threading.Thread(target=_send, daemon=True).start()
|
|
200
|
+
|
|
201
|
+
def asgi_middleware(self):
|
|
202
|
+
return SealASGIMiddleware
|
|
203
|
+
|
|
204
|
+
class SealASGIMiddleware:
|
|
205
|
+
def __init__(self, app):
|
|
206
|
+
self.app = app
|
|
207
|
+
self.seal = seal
|
|
208
|
+
self.auth_failures = {} # IP -> [timestamps]
|
|
209
|
+
import re
|
|
210
|
+
self.HONEYPOTS = ['/wp-admin', '/wp-login.php', '/.env', '/config.php', '/.git/config']
|
|
211
|
+
self.SQLI_REGEX = re.compile(r"(?:\b(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})\b)|(?:'|%27).*?(?:OR|AND).*?(?:'|%27)|(?:--)", re.IGNORECASE)
|
|
212
|
+
self.XSS_REGEX = re.compile(r"(?:<|%3C)script[\s\S]*?(?:>|%3E)|(?:<|%3C)[\s\S]*?(?:on[a-z]+\s*=)(?:>|%3E)", re.IGNORECASE)
|
|
213
|
+
self.TRAVERSAL_REGEX = re.compile(r"(?:\.\.\/|\.\.\\|%2e%2e%2f|%2e%2e%5c)", re.IGNORECASE)
|
|
214
|
+
self.SCANNER_REGEX = re.compile(r"(sqlmap|nikto|masscan|zmap|nmap|python-requests|curl|wget)", re.IGNORECASE)
|
|
215
|
+
self.ALLOWED_METHODS = [b'GET', b'POST', b'PUT', b'PATCH', b'DELETE', b'OPTIONS', b'HEAD']
|
|
216
|
+
|
|
217
|
+
async def _send_rejection(self, send, status_code, body_message):
|
|
218
|
+
await send({
|
|
219
|
+
"type": "http.response.start",
|
|
220
|
+
"status": status_code,
|
|
221
|
+
"headers": [(b"content-type", b"text/plain")]
|
|
222
|
+
})
|
|
223
|
+
await send({
|
|
224
|
+
"type": "http.response.body",
|
|
225
|
+
"body": body_message.encode("utf-8")
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
async def __call__(self, scope, receive, send):
|
|
229
|
+
if scope["type"] != "http":
|
|
230
|
+
return await self.app(scope, receive, send)
|
|
231
|
+
|
|
232
|
+
path = scope.get("path", "")
|
|
233
|
+
query = scope.get("query_string", b"").decode("utf-8", "ignore")
|
|
234
|
+
full_url = f"{path}?{query}" if query else path
|
|
235
|
+
method = scope.get("method", b"").encode("utf-8") if isinstance(scope.get("method"), str) else scope.get("method", b"")
|
|
236
|
+
|
|
237
|
+
client_ip = "unknown"
|
|
238
|
+
headers = dict(scope.get("headers", []))
|
|
239
|
+
if b"x-forwarded-for" in headers:
|
|
240
|
+
client_ip = headers[b"x-forwarded-for"].decode("utf-8", "ignore").split(",")[0].strip()
|
|
241
|
+
elif scope.get("client"):
|
|
242
|
+
client_ip = scope["client"][0]
|
|
243
|
+
|
|
244
|
+
ua = headers.get(b"user-agent", b"").decode("utf-8", "ignore")
|
|
245
|
+
cf_country = headers.get(b"cf-ipcountry", headers.get(b"x-vercel-ip-country", b"")).decode("utf-8", "ignore")
|
|
246
|
+
content_length = int(headers.get(b"content-length", b"0").decode("utf-8", "ignore") or 0)
|
|
247
|
+
|
|
248
|
+
waf_cfg = getattr(self.seal, 'waf_config', {})
|
|
249
|
+
|
|
250
|
+
# Geo-Blocking
|
|
251
|
+
if waf_cfg.get("geoBlocking", {}).get("blockedCountries") and cf_country in waf_cfg["geoBlocking"]["blockedCountries"]:
|
|
252
|
+
self.seal._report_threat("GEO_BLOCKED", client_ip, scope, {"country": cf_country})
|
|
253
|
+
if waf_cfg["geoBlocking"].get("action") == "drop":
|
|
254
|
+
return await self._send_rejection(send, 403, "Access Denied from your Region")
|
|
255
|
+
|
|
256
|
+
# Method Tampering
|
|
257
|
+
if waf_cfg.get("methodTampering", {}).get("action") == "drop" and method not in self.ALLOWED_METHODS:
|
|
258
|
+
self.seal._report_threat("METHOD_TAMPERING", client_ip, scope, {"method": method.decode("utf-8", "ignore")})
|
|
259
|
+
return await self._send_rejection(send, 405, "Method Not Allowed")
|
|
260
|
+
|
|
261
|
+
# Malicious Scanners
|
|
262
|
+
if waf_cfg.get("maliciousScanners", {}).get("action") == "drop" and self.SCANNER_REGEX.search(ua):
|
|
263
|
+
self.seal._report_threat("MALICIOUS_SCANNER", client_ip, scope, {"user_agent": ua})
|
|
264
|
+
return await self._send_rejection(send, 403, "Forbidden Scanner")
|
|
265
|
+
|
|
266
|
+
# Payload Overflow
|
|
267
|
+
max_payload = waf_cfg.get("payloadOverflow", {}).get("maxPayloadSize", 5242880)
|
|
268
|
+
if waf_cfg.get("payloadOverflow", {}).get("action") == "drop" and content_length > max_payload:
|
|
269
|
+
self.seal._report_threat("PAYLOAD_OVERFLOW", client_ip, scope, {"content_length": content_length})
|
|
270
|
+
return await self._send_rejection(send, 413, "Payload Too Large")
|
|
271
|
+
|
|
272
|
+
# Path Traversal
|
|
273
|
+
if waf_cfg.get("pathTraversal", {}).get("action") == "drop" and self.TRAVERSAL_REGEX.search(full_url):
|
|
274
|
+
self.seal._report_threat("PATH_TRAVERSAL", client_ip, scope)
|
|
275
|
+
return await self._send_rejection(send, 403, "Forbidden Path")
|
|
276
|
+
|
|
277
|
+
# 1. Honeypot check
|
|
278
|
+
if path in self.HONEYPOTS:
|
|
279
|
+
self.seal._report_threat("HONEYPOT_ACCESS", client_ip, scope)
|
|
280
|
+
|
|
281
|
+
# 2. WAF Legacy URL Check
|
|
282
|
+
is_threat = False
|
|
283
|
+
threat_type = ""
|
|
284
|
+
if self.SQLI_REGEX.search(full_url):
|
|
285
|
+
is_threat, threat_type = True, "SQL_INJECTION"
|
|
286
|
+
elif self.XSS_REGEX.search(full_url):
|
|
287
|
+
is_threat, threat_type = True, "XSS_ATTACK"
|
|
288
|
+
|
|
289
|
+
if is_threat:
|
|
290
|
+
self.seal._report_threat(threat_type, client_ip, scope)
|
|
291
|
+
|
|
292
|
+
# 3. 401/403 Sliding Window Tracker
|
|
293
|
+
async def send_wrapper(message):
|
|
294
|
+
if message["type"] == "http.response.start":
|
|
295
|
+
status = message.get("status")
|
|
296
|
+
if status in (401, 403):
|
|
297
|
+
now = time.time()
|
|
298
|
+
hits = self.auth_failures.get(client_ip, [])
|
|
299
|
+
hits.append(now)
|
|
300
|
+
recent_hits = [h for h in hits if h > now - 60]
|
|
301
|
+
self.auth_failures[client_ip] = recent_hits
|
|
302
|
+
|
|
303
|
+
if len(recent_hits) >= 10:
|
|
304
|
+
last_reported = self.auth_failures.get(f"reported_{client_ip}", 0)
|
|
305
|
+
if now - last_reported > 60:
|
|
306
|
+
self.seal._report_threat(
|
|
307
|
+
"BRUTE_FORCE_ATTACK",
|
|
308
|
+
client_ip,
|
|
309
|
+
scope,
|
|
310
|
+
{"status_code": status, "attempts": len(recent_hits)}
|
|
311
|
+
)
|
|
312
|
+
self.auth_failures[f"reported_{client_ip}"] = now
|
|
313
|
+
await send(message)
|
|
314
|
+
|
|
315
|
+
await self.app(scope, receive, send_wrapper)
|
|
316
|
+
|
|
317
|
+
# Global singleton
|
|
318
|
+
seal = SealClient()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from setuptools import setup, find_packages
|
|
2
|
+
|
|
3
|
+
with open("README.md", "r", encoding="utf-8") as fh:
|
|
4
|
+
long_description = fh.read()
|
|
5
|
+
|
|
6
|
+
setup(
|
|
7
|
+
name="desicon-seal",
|
|
8
|
+
version="1.0.2",
|
|
9
|
+
author="Seal Enterprise Security",
|
|
10
|
+
author_email="hello@circle-sure.com",
|
|
11
|
+
description="Zero-latency App-Layer WAF, Automated SRE Logging, and AI Rescue Engine.",
|
|
12
|
+
long_description=long_description,
|
|
13
|
+
long_description_content_type="text/markdown",
|
|
14
|
+
url="https://github.com/Desicon-AI/seal-python",
|
|
15
|
+
packages=find_packages(),
|
|
16
|
+
classifiers=[
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
],
|
|
21
|
+
python_requires='>=3.7',
|
|
22
|
+
install_requires=[
|
|
23
|
+
"requests>=2.25.1",
|
|
24
|
+
],
|
|
25
|
+
)
|