devfortress-sdk 4.2.0

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.
@@ -0,0 +1,217 @@
1
+ /**
2
+ * DevFortress SDK v3.0.0 — Type Definitions
3
+ *
4
+ * Covers:
5
+ * - Event types & severity levels
6
+ * - SDK configuration & enrichment hooks
7
+ * - Threat events & webhook payloads
8
+ * - Token alias interfaces
9
+ * - AbuseIPDB types
10
+ */
11
+ /** Closed-loop protection mode:
12
+ * - 'external': SDK → DevFortress Platform → webhook → response (default)
13
+ * - 'internal': 3-tier local engine, no platform calls (enterprise/air-gap)
14
+ * - 'hybrid': Internal evaluates first, external enriches asynchronously
15
+ */
16
+ export type CLMode = 'external' | 'internal' | 'hybrid';
17
+ export type EventType = 'auth_failure' | 'validation_error' | 'rate_limit_exceeded' | '5xx_error' | '4xx_error' | 'suspicious_pattern' | 'sql_injection_attempt' | 'xss_attempt' | 'custom' | 'login_brute_force' | 'credential_stuffing' | 'password_spray' | 'honeypot_triggered' | 'sql_injection' | 'auth_endpoint_scan' | 'recon_scan' | 'bot_signature' | 'brute_force' | 'token_replay' | 'privilege_escalation' | 'suspicious_ip' | 'anomalous_volume' | 'geo_anomaly';
18
+ export type SeverityLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
19
+ export type ThreatSeverity = 'low' | 'medium' | 'high' | 'critical';
20
+ /** Authentication phase when the event occurred:
21
+ * - 'pre_auth': Before authentication (login attempts, public endpoint attacks)
22
+ * - 'post_auth': After authentication (session-based threats, privilege escalation)
23
+ */
24
+ export type AuthPhase = 'pre_auth' | 'post_auth';
25
+ /** Action taken by the protection system in response to the threat */
26
+ export type ActionTaken = 'blocked' | 'rate_limited' | 'session_revoked' | 'captcha_challenged' | 'flagged' | 'allowed' | 'monitored';
27
+ export interface LiveThreatEvent {
28
+ eventType: EventType;
29
+ timestamp?: string;
30
+ ip: string;
31
+ method?: string;
32
+ path?: string;
33
+ userAgent?: string | null;
34
+ statusCode?: number | null;
35
+ responseTime?: number | null;
36
+ metadata?: Record<string, unknown>;
37
+ severity?: SeverityLevel;
38
+ reason?: string;
39
+ userId?: string | null;
40
+ sessionId?: string | null;
41
+ appId?: string;
42
+ environment?: string;
43
+ compositeScore?: number;
44
+ abuseipdb?: AbuseIPDBScore;
45
+ /** Closed-loop protection mode active when event was captured */
46
+ clMode?: CLMode;
47
+ /** Whether event occurred pre-auth or post-auth */
48
+ authPhase?: AuthPhase;
49
+ /** Action taken by the protection system */
50
+ actionTaken?: ActionTaken;
51
+ /** Response latency in milliseconds (time from request to SDK event emission) */
52
+ responseLatencyMs?: number;
53
+ /** Whether user session remained active after the action was taken */
54
+ sessionActiveAfterAction?: boolean;
55
+ }
56
+ export interface DevFortressClientOptions {
57
+ apiKey: string;
58
+ endpoint?: string;
59
+ timeout?: number;
60
+ retries?: number;
61
+ debug?: boolean;
62
+ }
63
+ export interface DevFortressConfig {
64
+ apiKey: string;
65
+ appId: string;
66
+ environment?: string;
67
+ debug?: boolean;
68
+ endpoint?: string;
69
+ timeout?: number;
70
+ retries?: number;
71
+ enrichment?: EnrichmentConfig;
72
+ alertThresholds?: Partial<Record<string, ThreatSeverity>>;
73
+ responseWebhook?: {
74
+ url: string;
75
+ secret: string;
76
+ };
77
+ cache?: {
78
+ driver: 'redis' | 'memory';
79
+ url?: string;
80
+ ttl?: number;
81
+ };
82
+ /** Closed-loop protection mode. Default: 'external' */
83
+ mode?: CLMode;
84
+ /** Subscription tier for feature gating */
85
+ tier?: 'starter' | 'pro' | 'enterprise';
86
+ /** Whether the hybrid add-on is enabled (Pro tier only) */
87
+ hybridAddonEnabled?: boolean;
88
+ /** Internal closed-loop engine configuration (for 'internal' or 'hybrid' mode) */
89
+ internalCL?: {
90
+ /** Fail-closed (block on error) or fail-open. Default: 'closed' */
91
+ failMode?: 'closed' | 'open';
92
+ /** Custom Tier 2 scorer */
93
+ tier2Scorer?: (request: unknown) => Promise<number> | number;
94
+ /** Score threshold for auto-block. Default: 85 */
95
+ blockThreshold?: number;
96
+ /** Rate limit: max requests per window. Default: 100 */
97
+ rateLimitMax?: number;
98
+ /** Rate limit window in ms. Default: 60000 */
99
+ rateLimitWindowMs?: number;
100
+ };
101
+ /** Circuit breaker config for hybrid mode fallback */
102
+ circuitBreaker?: {
103
+ /** Failures before opening circuit. Default: 3 */
104
+ failureThreshold?: number;
105
+ /** Time before testing recovery in ms. Default: 60000 */
106
+ recoveryTimeMs?: number;
107
+ };
108
+ }
109
+ export interface EnrichmentConfig {
110
+ getUserId?: (req: unknown) => string | null | Promise<string | null>;
111
+ getSessionId?: (req: unknown) => string | null | Promise<string | null>;
112
+ getIP?: (req: unknown) => string | null;
113
+ }
114
+ export interface ObserveResult {
115
+ flagged: boolean;
116
+ event_id: string;
117
+ confidence: number;
118
+ }
119
+ export interface ObserveOptions {
120
+ meta?: Record<string, unknown>;
121
+ skipEnrichment?: boolean;
122
+ }
123
+ export interface ThreatEvent {
124
+ event_id: string;
125
+ threat_type: string;
126
+ severity: ThreatSeverity;
127
+ confidence: number;
128
+ composite_score: number;
129
+ ip: string;
130
+ identity: {
131
+ user_id: string | null;
132
+ session_id: string | null;
133
+ };
134
+ geo?: {
135
+ country: string;
136
+ city: string;
137
+ };
138
+ abuseipdb?: AbuseIPDBScore;
139
+ endpoint: string;
140
+ method: string;
141
+ timestamp: string;
142
+ meta: Record<string, unknown>;
143
+ }
144
+ export interface ActionReport {
145
+ success: boolean;
146
+ actions: string[];
147
+ reason?: string;
148
+ meta?: Record<string, unknown>;
149
+ }
150
+ export interface AbuseIPDBScore {
151
+ score: number;
152
+ isTor: boolean;
153
+ isDatacenter: boolean;
154
+ distinctUsers?: number;
155
+ fromCache?: boolean;
156
+ }
157
+ export interface AbuseIPDBCheckResult {
158
+ abuseConfidenceScore: number;
159
+ totalReports: number;
160
+ numDistinctUsers: number;
161
+ isTor: boolean;
162
+ usageType: string;
163
+ countryCode: string;
164
+ lastReportedAt: string | null;
165
+ isp: string;
166
+ }
167
+ export interface CompositeScoreResult {
168
+ score: number;
169
+ severity: ThreatSeverity;
170
+ abuseScore: number;
171
+ isTor: boolean;
172
+ isDatacenter: boolean;
173
+ distinctUsers: number;
174
+ fromCache: boolean;
175
+ }
176
+ export interface TokenAliasData {
177
+ realToken: string;
178
+ userId: string;
179
+ createdAt: number;
180
+ }
181
+ export interface DevFortressMiddlewareOptions extends DevFortressClientOptions {
182
+ captureBody?: boolean;
183
+ captureHeaders?: boolean;
184
+ excludePaths?: string[];
185
+ sanitize?: (data: Record<string, unknown>) => Record<string, unknown>;
186
+ onRequest?: (req: unknown) => Partial<LiveThreatEvent> | null;
187
+ onError?: (error: Error) => void;
188
+ mode?: CLMode;
189
+ }
190
+ export interface ApiResponse {
191
+ success: boolean;
192
+ message?: string;
193
+ eventId?: string;
194
+ }
195
+ export interface WebhookPayload {
196
+ event_id: string;
197
+ action: string;
198
+ threat_type: string;
199
+ severity: ThreatSeverity;
200
+ confidence: number;
201
+ composite_score: number;
202
+ ip: string;
203
+ identity: {
204
+ user_id: string | null;
205
+ session_id: string | null;
206
+ };
207
+ geo?: {
208
+ country: string;
209
+ city: string;
210
+ };
211
+ abuseipdb?: AbuseIPDBScore;
212
+ endpoint: string;
213
+ method: string;
214
+ timestamp: string;
215
+ meta: Record<string, unknown>;
216
+ }
217
+ export type ThreatHandler = (event: ThreatEvent) => Promise<void> | void;
package/dist/types.js ADDED
@@ -0,0 +1,12 @@
1
+ "use strict";
2
+ /**
3
+ * DevFortress SDK v3.0.0 — Type Definitions
4
+ *
5
+ * Covers:
6
+ * - Event types & severity levels
7
+ * - SDK configuration & enrichment hooks
8
+ * - Threat events & webhook payloads
9
+ * - Token alias interfaces
10
+ * - AbuseIPDB types
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json ADDED
@@ -0,0 +1,101 @@
1
+ {
2
+ "name": "devfortress-sdk",
3
+ "version": "4.2.0",
4
+ "description": "DevFortress SDK — API and application security with automated threat response, session privacy, and AI agent observability.",
5
+ "main": "dist/index.js",
6
+ "browser": "dist/browser.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ },
14
+ "./browser": {
15
+ "import": "./dist/browser.js",
16
+ "require": "./dist/browser.js",
17
+ "types": "./dist/browser.d.ts"
18
+ },
19
+ "./quick": {
20
+ "import": "./dist/quick.js",
21
+ "require": "./dist/quick.js",
22
+ "types": "./dist/quick.d.ts"
23
+ },
24
+ "./package.json": "./package.json"
25
+ },
26
+ "bin": {
27
+ "devfortress-init": "./bin/devfortress-init.js"
28
+ },
29
+ "files": [
30
+ "dist/index.js",
31
+ "dist/index.d.ts",
32
+ "dist/client.js",
33
+ "dist/client.d.ts",
34
+ "dist/browser.js",
35
+ "dist/browser.d.ts",
36
+ "dist/quick.js",
37
+ "dist/quick.d.ts",
38
+ "dist/types.js",
39
+ "dist/types.d.ts",
40
+ "dist/circuit-breaker.js",
41
+ "dist/circuit-breaker.d.ts",
42
+ "dist/middleware/express.js",
43
+ "dist/middleware/express.d.ts",
44
+ "bin/devfortress-init.js",
45
+ "src/middleware/fastapi.py",
46
+ "src/middleware/flask.py",
47
+ "README.md",
48
+ "LICENSE"
49
+ ],
50
+ "scripts": {
51
+ "build": "tsc",
52
+ "test": "jest",
53
+ "prepublishOnly": "npm run build"
54
+ },
55
+ "keywords": [
56
+ "devfortress",
57
+ "security",
58
+ "api-security",
59
+ "application-security",
60
+ "threat-detection",
61
+ "monitoring",
62
+ "automated-response",
63
+ "webhook",
64
+ "express",
65
+ "nodejs",
66
+ "agent-security",
67
+ "ai-agents",
68
+ "closed-loop",
69
+ "session-privacy"
70
+ ],
71
+ "author": "DevFortress Team",
72
+ "license": "BUSL-1.1",
73
+ "repository": {
74
+ "type": "git",
75
+ "url": "https://github.com/duncan982/devfortress.git",
76
+ "directory": "packages/devfortress-sdk"
77
+ },
78
+ "bugs": {
79
+ "url": "https://github.com/duncan982/devfortress/issues"
80
+ },
81
+ "homepage": "https://devfortress.net",
82
+ "sideEffects": false,
83
+ "dependencies": {
84
+ "axios": "^1.6.0"
85
+ },
86
+ "devDependencies": {
87
+ "@types/node": "^20.0.0",
88
+ "@types/express": "^4.17.21 || ^5.0.0",
89
+ "typescript": "^5.0.0",
90
+ "jest": "^30.2.0",
91
+ "@types/jest": "^30.0.0"
92
+ },
93
+ "peerDependencies": {
94
+ "express": "^4.18.0 || ^5.0.0"
95
+ },
96
+ "peerDependenciesMeta": {
97
+ "express": {
98
+ "optional": true
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,232 @@
1
+ """
2
+ DevFortress FastAPI Middleware
3
+
4
+ Automatically monitors FastAPI applications for security events and sends
5
+ them to the DevFortress surveillance API.
6
+
7
+ Usage:
8
+ from devfortress_middleware import DevFortressMiddleware
9
+
10
+ app = FastAPI()
11
+ app.add_middleware(
12
+ DevFortressMiddleware,
13
+ api_key="your-api-key",
14
+ endpoint="https://www.devfortress.net/api/events/ingest"
15
+ )
16
+ """
17
+
18
+ import time
19
+ import re
20
+ import json
21
+ import asyncio
22
+ from typing import Optional, List, Dict, Any, Callable
23
+ from starlette.middleware.base import BaseHTTPMiddleware
24
+ from starlette.requests import Request
25
+ from starlette.responses import Response
26
+
27
+ try:
28
+ import httpx
29
+ HAS_HTTPX = True
30
+ except ImportError:
31
+ HAS_HTTPX = False
32
+
33
+ try:
34
+ from urllib.request import Request as URLRequest, urlopen
35
+ from urllib.error import URLError
36
+ HAS_URLLIB = True
37
+ except ImportError:
38
+ HAS_URLLIB = False
39
+
40
+
41
+ # SQL Injection patterns
42
+ SQL_PATTERNS = [
43
+ re.compile(r"(\bor\b.*=.*)", re.IGNORECASE),
44
+ re.compile(r"(\bunion\b.*\bselect\b)", re.IGNORECASE),
45
+ re.compile(r"(\bdrop\b.*\btable\b)", re.IGNORECASE),
46
+ re.compile(r"('\s*or\s*'1'\s*=\s*'1)", re.IGNORECASE),
47
+ ]
48
+
49
+ # XSS patterns
50
+ XSS_PATTERNS = [
51
+ re.compile(r"<script[^>]*>[\s\S]*?</script>", re.IGNORECASE),
52
+ re.compile(r"javascript:", re.IGNORECASE),
53
+ re.compile(r"onerror\s*=\s*", re.IGNORECASE),
54
+ re.compile(r"onload\s*=\s*", re.IGNORECASE),
55
+ ]
56
+
57
+ # Path traversal patterns
58
+ TRAVERSAL_PATTERNS = [
59
+ re.compile(r"\.\.[/\\]"),
60
+ re.compile(r"/etc/passwd", re.IGNORECASE),
61
+ ]
62
+
63
+
64
+ class DevFortressMiddleware(BaseHTTPMiddleware):
65
+ """FastAPI middleware for DevFortress security monitoring."""
66
+
67
+ def __init__(
68
+ self,
69
+ app,
70
+ api_key: str,
71
+ endpoint: str = "https://www.devfortress.net/api/events/ingest",
72
+ exclude_paths: Optional[List[str]] = None,
73
+ capture_headers: bool = False,
74
+ debug: bool = False,
75
+ on_error: Optional[Callable[[Exception], None]] = None,
76
+ ):
77
+ super().__init__(app)
78
+ self.api_key = api_key
79
+ self.endpoint = endpoint
80
+ self.exclude_paths = exclude_paths or ["/health", "/docs", "/openapi.json"]
81
+ self.capture_headers = capture_headers
82
+ self.debug = debug
83
+ self.on_error = on_error
84
+
85
+ async def dispatch(self, request: Request, call_next) -> Response:
86
+ # Skip excluded paths
87
+ if any(request.url.path.startswith(p) for p in self.exclude_paths):
88
+ return await call_next(request)
89
+
90
+ start_time = time.time()
91
+ response: Optional[Response] = None
92
+
93
+ try:
94
+ response = await call_next(request)
95
+ except Exception as exc:
96
+ # Track server error
97
+ await self._track_event(
98
+ request=request,
99
+ status_code=500,
100
+ response_time=time.time() - start_time,
101
+ event_type="5xx_error",
102
+ severity="HIGH",
103
+ reason=f"Unhandled exception: {str(exc)}",
104
+ )
105
+ raise
106
+
107
+ response_time = time.time() - start_time
108
+ status_code = response.status_code
109
+
110
+ # Determine event type
111
+ event_type = None
112
+ severity = "LOW"
113
+ reason = None
114
+
115
+ if status_code in (401, 403):
116
+ event_type = "auth_failure"
117
+ severity = "MEDIUM"
118
+ reason = "Authentication or authorization failed"
119
+ elif status_code in (400, 422):
120
+ event_type = "validation_error"
121
+ severity = "LOW"
122
+ reason = "Request validation failed"
123
+ elif status_code == 429:
124
+ event_type = "rate_limit_exceeded"
125
+ severity = "MEDIUM"
126
+ reason = "Rate limit exceeded"
127
+ elif status_code >= 500:
128
+ event_type = "5xx_error"
129
+ severity = "HIGH"
130
+ reason = f"Server error: {status_code}"
131
+ elif status_code >= 400:
132
+ event_type = "4xx_error"
133
+ severity = "LOW"
134
+ reason = f"Client error: {status_code}"
135
+
136
+ # Check suspicious patterns
137
+ suspicious = self._detect_suspicious(request)
138
+ if suspicious:
139
+ event_type = "suspicious_pattern"
140
+ severity = "HIGH"
141
+ reason = f"Suspicious patterns: {', '.join(suspicious)}"
142
+
143
+ if event_type:
144
+ # Fire and forget
145
+ asyncio.create_task(
146
+ self._track_event(
147
+ request=request,
148
+ status_code=status_code,
149
+ response_time=response_time,
150
+ event_type=event_type,
151
+ severity=severity,
152
+ reason=reason,
153
+ )
154
+ )
155
+
156
+ return response
157
+
158
+ async def _track_event(
159
+ self,
160
+ request: Request,
161
+ status_code: int,
162
+ response_time: float,
163
+ event_type: str,
164
+ severity: str,
165
+ reason: Optional[str] = None,
166
+ ):
167
+ """Send event to DevFortress API."""
168
+ ip = (
169
+ request.headers.get("x-forwarded-for", "").split(",")[0].strip()
170
+ or request.headers.get("x-real-ip", "")
171
+ or (request.client.host if request.client else "0.0.0.0")
172
+ )
173
+
174
+ payload: Dict[str, Any] = {
175
+ "eventType": event_type,
176
+ "ip": ip,
177
+ "method": request.method,
178
+ "path": str(request.url.path),
179
+ "userAgent": request.headers.get("user-agent"),
180
+ "statusCode": status_code,
181
+ "responseTime": round(response_time * 1000),
182
+ "severity": severity,
183
+ "reason": reason,
184
+ "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime()),
185
+ }
186
+
187
+ if self.capture_headers:
188
+ payload["metadata"] = {"headers": dict(request.headers)}
189
+
190
+ try:
191
+ if HAS_HTTPX:
192
+ async with httpx.AsyncClient(timeout=5.0) as client:
193
+ await client.post(
194
+ self.endpoint,
195
+ json=payload,
196
+ headers={
197
+ "Content-Type": "application/json",
198
+ "X-DevFortress-Key": self.api_key,
199
+ },
200
+ )
201
+ elif HAS_URLLIB:
202
+ req = URLRequest(
203
+ self.endpoint,
204
+ data=json.dumps(payload).encode("utf-8"),
205
+ headers={
206
+ "Content-Type": "application/json",
207
+ "X-DevFortress-Key": self.api_key,
208
+ },
209
+ method="POST",
210
+ )
211
+ urlopen(req, timeout=5)
212
+ except Exception as exc:
213
+ if self.on_error:
214
+ self.on_error(exc)
215
+ elif self.debug:
216
+ print(f"[DevFortress] Failed to send event: {exc}") # noqa: T201
217
+
218
+ def _detect_suspicious(self, request: Request) -> List[str]:
219
+ """Detect suspicious patterns in the request."""
220
+ patterns_found = []
221
+ url_str = str(request.url)
222
+ query_str = str(request.query_params)
223
+ check_str = f"{url_str} {query_str}"
224
+
225
+ if any(p.search(check_str) for p in SQL_PATTERNS):
226
+ patterns_found.append("sql_injection")
227
+ if any(p.search(check_str) for p in XSS_PATTERNS):
228
+ patterns_found.append("xss")
229
+ if any(p.search(check_str) for p in TRAVERSAL_PATTERNS):
230
+ patterns_found.append("path_traversal")
231
+
232
+ return patterns_found