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.
- package/LICENSE +63 -0
- package/README.md +474 -0
- package/bin/devfortress-init.js +206 -0
- package/dist/browser.d.ts +61 -0
- package/dist/browser.js +184 -0
- package/dist/circuit-breaker.d.ts +68 -0
- package/dist/circuit-breaker.js +116 -0
- package/dist/client.d.ts +26 -0
- package/dist/client.js +98 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +78 -0
- package/dist/middleware/express.d.ts +9 -0
- package/dist/middleware/express.js +236 -0
- package/dist/quick.d.ts +37 -0
- package/dist/quick.js +135 -0
- package/dist/types.d.ts +217 -0
- package/dist/types.js +12 -0
- package/package.json +101 -0
- package/src/middleware/fastapi.py +232 -0
- package/src/middleware/flask.py +213 -0
package/dist/client.js
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DevFortress API Client
|
|
4
|
+
* Core client for sending events to DevFortress surveillance API
|
|
5
|
+
*/
|
|
6
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
|
+
};
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.DevFortressClient = void 0;
|
|
11
|
+
const axios_1 = __importDefault(require("axios"));
|
|
12
|
+
const DEFAULT_ENDPOINT = 'https://www.devfortress.net/api/events/ingest';
|
|
13
|
+
const DEFAULT_TIMEOUT = 5000;
|
|
14
|
+
const DEFAULT_RETRIES = 3;
|
|
15
|
+
class DevFortressClient {
|
|
16
|
+
constructor(options) {
|
|
17
|
+
if (!options.apiKey) {
|
|
18
|
+
throw new Error('DevFortressClient: apiKey is required');
|
|
19
|
+
}
|
|
20
|
+
this.apiKey = options.apiKey;
|
|
21
|
+
this.endpoint = options.endpoint || DEFAULT_ENDPOINT;
|
|
22
|
+
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
23
|
+
this.retries = options.retries || DEFAULT_RETRIES;
|
|
24
|
+
this.debug = options.debug || false;
|
|
25
|
+
// Warn if endpoint is not HTTPS
|
|
26
|
+
if (this.endpoint.startsWith('http://') &&
|
|
27
|
+
!this.endpoint.includes('localhost') &&
|
|
28
|
+
!this.endpoint.includes('127.0.0.1')) {
|
|
29
|
+
// eslint-disable-next-line no-console
|
|
30
|
+
console.warn('[DevFortress] WARNING: Using non-HTTPS endpoint. API key will be transmitted in cleartext.');
|
|
31
|
+
}
|
|
32
|
+
this.axios = axios_1.default.create({
|
|
33
|
+
baseURL: this.endpoint,
|
|
34
|
+
timeout: this.timeout,
|
|
35
|
+
headers: {
|
|
36
|
+
'Content-Type': 'application/json',
|
|
37
|
+
'X-DevFortress-Key': this.apiKey,
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Track a security event
|
|
43
|
+
*/
|
|
44
|
+
async trackEvent(event) {
|
|
45
|
+
try {
|
|
46
|
+
// Add timestamp if not provided
|
|
47
|
+
const payload = {
|
|
48
|
+
...event,
|
|
49
|
+
timestamp: event.timestamp || new Date().toISOString(),
|
|
50
|
+
};
|
|
51
|
+
const response = await this.sendWithRetry(payload);
|
|
52
|
+
return response.data;
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
throw new Error(`Failed to track event: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Send event with automatic retry logic
|
|
60
|
+
*/
|
|
61
|
+
async sendWithRetry(payload, attempt = 1) {
|
|
62
|
+
try {
|
|
63
|
+
const response = await this.axios.post('', payload);
|
|
64
|
+
return response;
|
|
65
|
+
}
|
|
66
|
+
catch (error) {
|
|
67
|
+
const axiosError = error;
|
|
68
|
+
// Don't retry on client errors (4xx)
|
|
69
|
+
if (axiosError.response?.status && axiosError.response.status < 500) {
|
|
70
|
+
throw error;
|
|
71
|
+
}
|
|
72
|
+
// Retry on server errors (5xx) or network errors
|
|
73
|
+
if (attempt < this.retries) {
|
|
74
|
+
const backoff = Math.pow(2, attempt) * 1000; // Exponential backoff
|
|
75
|
+
await new Promise(resolve => setTimeout(resolve, backoff));
|
|
76
|
+
return this.sendWithRetry(payload, attempt + 1);
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Test connection to surveillance API
|
|
83
|
+
*/
|
|
84
|
+
async testConnection() {
|
|
85
|
+
try {
|
|
86
|
+
await this.trackEvent({
|
|
87
|
+
eventType: 'custom',
|
|
88
|
+
ip: '127.0.0.1',
|
|
89
|
+
metadata: { test: true },
|
|
90
|
+
});
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
exports.DevFortressClient = DevFortressClient;
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevFortress SDK v4.0.0 — Main Entry Point (Node.js)
|
|
3
|
+
*
|
|
4
|
+
* For browser usage, import from 'devfortress-sdk/browser' instead.
|
|
5
|
+
* For zero-config quick start, import from 'devfortress-sdk/quick'.
|
|
6
|
+
* For Express middleware, use devfortressMiddleware from this package.
|
|
7
|
+
* For FastAPI/Flask, see the Python middleware in src/middleware/
|
|
8
|
+
*
|
|
9
|
+
* NEW in v4.0 — Closed-Loop Mode Architecture:
|
|
10
|
+
* - Three protection modes: external, internal, hybrid
|
|
11
|
+
* - Circuit breaker with automatic failover (hybrid mode)
|
|
12
|
+
* - Unified audit trail (internal + external merged timeline)
|
|
13
|
+
* - Tier-gated feature access (Starter/Pro/Enterprise)
|
|
14
|
+
* - Backward compatible — existing external-mode code works unchanged
|
|
15
|
+
*
|
|
16
|
+
* v3.3:
|
|
17
|
+
* - Internal Closed-Loop Engine (3-tier deterministic + ML + async relay)
|
|
18
|
+
* - Air-gap mode support (no external dependencies in Tier 1 + 2)
|
|
19
|
+
*
|
|
20
|
+
* v3.2:
|
|
21
|
+
* - Agent security (AgentAdapter, credential management, anomaly detection)
|
|
22
|
+
*
|
|
23
|
+
* v3.1:
|
|
24
|
+
* - Zero-config quick start (df.init({ apiKey }))
|
|
25
|
+
* - Privacy-strict mode, Debug mode
|
|
26
|
+
*
|
|
27
|
+
* v3.0:
|
|
28
|
+
* - DevFortress class with observe(), isBlocked(), onThreatDetected()
|
|
29
|
+
* - AbuseIPDB integration, Token alias security, Webhook handler
|
|
30
|
+
*
|
|
31
|
+
* @packageDocumentation
|
|
32
|
+
*/
|
|
33
|
+
export { init, getInstance, getDataSnapshot } from './quick';
|
|
34
|
+
export type { QuickConfig } from './quick';
|
|
35
|
+
export { DevFortress } from './devfortress';
|
|
36
|
+
export { PlatformCircuitBreaker } from './circuit-breaker';
|
|
37
|
+
export type { CircuitState, CircuitBreakerConfig } from './circuit-breaker';
|
|
38
|
+
export { UnifiedAuditTrail } from './unified-audit';
|
|
39
|
+
export type { AuditSource, UnifiedAuditEntry, UnifiedAuditConfig, } from './unified-audit';
|
|
40
|
+
export { TierGate } from './tier-gate';
|
|
41
|
+
export type { SubscriptionTier, TierCapabilities } from './tier-gate';
|
|
42
|
+
export { checkIP, reportIP, syncBlacklist, scoreThreat, getCachedScore, } from './abuseipdb';
|
|
43
|
+
export { TokenAliasManager, EmergencyBlocklist } from './token-alias';
|
|
44
|
+
export type { CacheAdapter, TokenAliasManagerConfig, RevocationReason, } from './token-alias';
|
|
45
|
+
export { AgentAdapter, validateAgentId } from './agent';
|
|
46
|
+
export type { AgentToolCall, AgentAdapterConfig } from './agent';
|
|
47
|
+
export { AgentCredentialManager, AgentBaselineEngine, AgentAnomalyDetector, AgentScopeEnforcer, } from './agent-security';
|
|
48
|
+
export type { AgentCredentialType, AgentCredentialAliasData, AgentBaselineMetrics, AgentSessionRecord, AgentAnomalyType, AgentAnomalySignal, AgentScopeDefinition, } from './agent-security';
|
|
49
|
+
export { InternalClosedLoopEngine } from './internal-closed-loop-engine';
|
|
50
|
+
export type { InternalCLConfig, InternalCLRequest, InternalCLResult, InternalCLDecision, InternalCLAction, InternalCLAuditEntry, InternalCLRule, } from './internal-closed-loop-engine';
|
|
51
|
+
export { DevFortressClient } from './client';
|
|
52
|
+
export { devfortressMiddleware } from './middleware/express';
|
|
53
|
+
export type { CLMode, DevFortressConfig, EnrichmentConfig, ObserveResult, ObserveOptions, ThreatEvent, ThreatSeverity, ThreatHandler, ActionReport, WebhookPayload, AbuseIPDBScore, AbuseIPDBCheckResult, CompositeScoreResult, TokenAliasData, DevFortressClientOptions, LiveThreatEvent, EventType, SeverityLevel, DevFortressMiddlewareOptions, ApiResponse, } from './types';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DevFortress SDK v4.0.0 — Main Entry Point (Node.js)
|
|
4
|
+
*
|
|
5
|
+
* For browser usage, import from 'devfortress-sdk/browser' instead.
|
|
6
|
+
* For zero-config quick start, import from 'devfortress-sdk/quick'.
|
|
7
|
+
* For Express middleware, use devfortressMiddleware from this package.
|
|
8
|
+
* For FastAPI/Flask, see the Python middleware in src/middleware/
|
|
9
|
+
*
|
|
10
|
+
* NEW in v4.0 — Closed-Loop Mode Architecture:
|
|
11
|
+
* - Three protection modes: external, internal, hybrid
|
|
12
|
+
* - Circuit breaker with automatic failover (hybrid mode)
|
|
13
|
+
* - Unified audit trail (internal + external merged timeline)
|
|
14
|
+
* - Tier-gated feature access (Starter/Pro/Enterprise)
|
|
15
|
+
* - Backward compatible — existing external-mode code works unchanged
|
|
16
|
+
*
|
|
17
|
+
* v3.3:
|
|
18
|
+
* - Internal Closed-Loop Engine (3-tier deterministic + ML + async relay)
|
|
19
|
+
* - Air-gap mode support (no external dependencies in Tier 1 + 2)
|
|
20
|
+
*
|
|
21
|
+
* v3.2:
|
|
22
|
+
* - Agent security (AgentAdapter, credential management, anomaly detection)
|
|
23
|
+
*
|
|
24
|
+
* v3.1:
|
|
25
|
+
* - Zero-config quick start (df.init({ apiKey }))
|
|
26
|
+
* - Privacy-strict mode, Debug mode
|
|
27
|
+
*
|
|
28
|
+
* v3.0:
|
|
29
|
+
* - DevFortress class with observe(), isBlocked(), onThreatDetected()
|
|
30
|
+
* - AbuseIPDB integration, Token alias security, Webhook handler
|
|
31
|
+
*
|
|
32
|
+
* @packageDocumentation
|
|
33
|
+
*/
|
|
34
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
35
|
+
exports.devfortressMiddleware = exports.DevFortressClient = exports.InternalClosedLoopEngine = exports.AgentScopeEnforcer = exports.AgentAnomalyDetector = exports.AgentBaselineEngine = exports.AgentCredentialManager = exports.validateAgentId = exports.AgentAdapter = exports.EmergencyBlocklist = exports.TokenAliasManager = exports.getCachedScore = exports.scoreThreat = exports.syncBlacklist = exports.reportIP = exports.checkIP = exports.TierGate = exports.UnifiedAuditTrail = exports.PlatformCircuitBreaker = exports.DevFortress = exports.getDataSnapshot = exports.getInstance = exports.init = void 0;
|
|
36
|
+
// v3.1 — Zero-config quick start
|
|
37
|
+
var quick_1 = require("./quick");
|
|
38
|
+
Object.defineProperty(exports, "init", { enumerable: true, get: function () { return quick_1.init; } });
|
|
39
|
+
Object.defineProperty(exports, "getInstance", { enumerable: true, get: function () { return quick_1.getInstance; } });
|
|
40
|
+
Object.defineProperty(exports, "getDataSnapshot", { enumerable: true, get: function () { return quick_1.getDataSnapshot; } });
|
|
41
|
+
// v3.0 — Primary export
|
|
42
|
+
var devfortress_1 = require("./devfortress");
|
|
43
|
+
Object.defineProperty(exports, "DevFortress", { enumerable: true, get: function () { return devfortress_1.DevFortress; } });
|
|
44
|
+
// v4.0 — Closed-Loop Mode Architecture
|
|
45
|
+
var circuit_breaker_1 = require("./circuit-breaker");
|
|
46
|
+
Object.defineProperty(exports, "PlatformCircuitBreaker", { enumerable: true, get: function () { return circuit_breaker_1.PlatformCircuitBreaker; } });
|
|
47
|
+
var unified_audit_1 = require("./unified-audit");
|
|
48
|
+
Object.defineProperty(exports, "UnifiedAuditTrail", { enumerable: true, get: function () { return unified_audit_1.UnifiedAuditTrail; } });
|
|
49
|
+
var tier_gate_1 = require("./tier-gate");
|
|
50
|
+
Object.defineProperty(exports, "TierGate", { enumerable: true, get: function () { return tier_gate_1.TierGate; } });
|
|
51
|
+
// v3.0 — AbuseIPDB integration
|
|
52
|
+
var abuseipdb_1 = require("./abuseipdb");
|
|
53
|
+
Object.defineProperty(exports, "checkIP", { enumerable: true, get: function () { return abuseipdb_1.checkIP; } });
|
|
54
|
+
Object.defineProperty(exports, "reportIP", { enumerable: true, get: function () { return abuseipdb_1.reportIP; } });
|
|
55
|
+
Object.defineProperty(exports, "syncBlacklist", { enumerable: true, get: function () { return abuseipdb_1.syncBlacklist; } });
|
|
56
|
+
Object.defineProperty(exports, "scoreThreat", { enumerable: true, get: function () { return abuseipdb_1.scoreThreat; } });
|
|
57
|
+
Object.defineProperty(exports, "getCachedScore", { enumerable: true, get: function () { return abuseipdb_1.getCachedScore; } });
|
|
58
|
+
// v3.0 — Token alias security
|
|
59
|
+
var token_alias_1 = require("./token-alias");
|
|
60
|
+
Object.defineProperty(exports, "TokenAliasManager", { enumerable: true, get: function () { return token_alias_1.TokenAliasManager; } });
|
|
61
|
+
Object.defineProperty(exports, "EmergencyBlocklist", { enumerable: true, get: function () { return token_alias_1.EmergencyBlocklist; } });
|
|
62
|
+
// v3.2 — Agent Security (IP-C1 through IP-C5)
|
|
63
|
+
var agent_1 = require("./agent");
|
|
64
|
+
Object.defineProperty(exports, "AgentAdapter", { enumerable: true, get: function () { return agent_1.AgentAdapter; } });
|
|
65
|
+
Object.defineProperty(exports, "validateAgentId", { enumerable: true, get: function () { return agent_1.validateAgentId; } });
|
|
66
|
+
var agent_security_1 = require("./agent-security");
|
|
67
|
+
Object.defineProperty(exports, "AgentCredentialManager", { enumerable: true, get: function () { return agent_security_1.AgentCredentialManager; } });
|
|
68
|
+
Object.defineProperty(exports, "AgentBaselineEngine", { enumerable: true, get: function () { return agent_security_1.AgentBaselineEngine; } });
|
|
69
|
+
Object.defineProperty(exports, "AgentAnomalyDetector", { enumerable: true, get: function () { return agent_security_1.AgentAnomalyDetector; } });
|
|
70
|
+
Object.defineProperty(exports, "AgentScopeEnforcer", { enumerable: true, get: function () { return agent_security_1.AgentScopeEnforcer; } });
|
|
71
|
+
// v3.3 — Internal Closed-Loop Engine
|
|
72
|
+
var internal_closed_loop_engine_1 = require("./internal-closed-loop-engine");
|
|
73
|
+
Object.defineProperty(exports, "InternalClosedLoopEngine", { enumerable: true, get: function () { return internal_closed_loop_engine_1.InternalClosedLoopEngine; } });
|
|
74
|
+
// v1.x — Backward compatible exports
|
|
75
|
+
var client_1 = require("./client");
|
|
76
|
+
Object.defineProperty(exports, "DevFortressClient", { enumerable: true, get: function () { return client_1.DevFortressClient; } });
|
|
77
|
+
var express_1 = require("./middleware/express");
|
|
78
|
+
Object.defineProperty(exports, "devfortressMiddleware", { enumerable: true, get: function () { return express_1.devfortressMiddleware; } });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Express.js Middleware for DevFortress Surveillance
|
|
3
|
+
*/
|
|
4
|
+
import { Request, Response, NextFunction } from 'express';
|
|
5
|
+
import type { DevFortressMiddlewareOptions } from '../types';
|
|
6
|
+
/**
|
|
7
|
+
* Create Express middleware for automatic threat monitoring
|
|
8
|
+
*/
|
|
9
|
+
export declare function devfortressMiddleware(options: DevFortressMiddlewareOptions): (req: Request, res: Response, next: NextFunction) => Promise<void>;
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Express.js Middleware for DevFortress Surveillance
|
|
4
|
+
*/
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.devfortressMiddleware = devfortressMiddleware;
|
|
7
|
+
const client_1 = require("../client");
|
|
8
|
+
/**
|
|
9
|
+
* Create Express middleware for automatic threat monitoring
|
|
10
|
+
*/
|
|
11
|
+
function devfortressMiddleware(options) {
|
|
12
|
+
const client = new client_1.DevFortressClient(options);
|
|
13
|
+
const { captureBody = false, captureHeaders = false, excludePaths = [], sanitize, onRequest, onError, mode = 'external', } = options;
|
|
14
|
+
return async function devfortressMiddlewareHandler(req, res, next) {
|
|
15
|
+
// Skip excluded paths
|
|
16
|
+
if (excludePaths.some(path => req.path.startsWith(path))) {
|
|
17
|
+
return next();
|
|
18
|
+
}
|
|
19
|
+
const startTime = Date.now();
|
|
20
|
+
// Capture response
|
|
21
|
+
const originalSend = res.send;
|
|
22
|
+
res.send = function (body) {
|
|
23
|
+
return originalSend.call(this, body);
|
|
24
|
+
};
|
|
25
|
+
// Wait for response to finish
|
|
26
|
+
res.on('finish', async () => {
|
|
27
|
+
try {
|
|
28
|
+
const responseTime = Date.now() - startTime;
|
|
29
|
+
const statusCode = res.statusCode;
|
|
30
|
+
// Determine if this is a security event
|
|
31
|
+
let eventType = null;
|
|
32
|
+
let severity = 'LOW';
|
|
33
|
+
let reason;
|
|
34
|
+
// Check for authentication failures
|
|
35
|
+
if (statusCode === 401 || statusCode === 403) {
|
|
36
|
+
eventType = 'auth_failure';
|
|
37
|
+
severity = 'MEDIUM';
|
|
38
|
+
reason = 'Authentication or authorization failed';
|
|
39
|
+
}
|
|
40
|
+
// Check for validation errors
|
|
41
|
+
else if (statusCode === 400 || statusCode === 422) {
|
|
42
|
+
eventType = 'validation_error';
|
|
43
|
+
severity = 'LOW';
|
|
44
|
+
reason = 'Request validation failed';
|
|
45
|
+
}
|
|
46
|
+
// Check for rate limiting
|
|
47
|
+
else if (statusCode === 429) {
|
|
48
|
+
eventType = 'rate_limit_exceeded';
|
|
49
|
+
severity = 'MEDIUM';
|
|
50
|
+
reason = 'Rate limit exceeded';
|
|
51
|
+
}
|
|
52
|
+
// Check for server errors
|
|
53
|
+
else if (statusCode >= 500) {
|
|
54
|
+
eventType = '5xx_error';
|
|
55
|
+
severity = 'HIGH';
|
|
56
|
+
reason = `Server error: ${statusCode}`;
|
|
57
|
+
}
|
|
58
|
+
// Check for client errors
|
|
59
|
+
else if (statusCode >= 400) {
|
|
60
|
+
eventType = '4xx_error';
|
|
61
|
+
severity = 'LOW';
|
|
62
|
+
reason = `Client error: ${statusCode}`;
|
|
63
|
+
}
|
|
64
|
+
// Check for suspicious patterns
|
|
65
|
+
const suspiciousPatterns = detectSuspiciousPatterns(req);
|
|
66
|
+
if (suspiciousPatterns.length > 0) {
|
|
67
|
+
eventType = 'suspicious_pattern';
|
|
68
|
+
severity = 'HIGH';
|
|
69
|
+
reason = `Suspicious patterns detected: ${suspiciousPatterns.join(', ')}`;
|
|
70
|
+
}
|
|
71
|
+
// Allow custom event detection
|
|
72
|
+
if (onRequest) {
|
|
73
|
+
const customEvent = onRequest(req);
|
|
74
|
+
if (customEvent) {
|
|
75
|
+
eventType = customEvent.eventType || eventType;
|
|
76
|
+
severity = customEvent.severity || severity;
|
|
77
|
+
reason = customEvent.reason || reason;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Only track if we detected an event
|
|
81
|
+
if (eventType) {
|
|
82
|
+
let metadata = {
|
|
83
|
+
method: req.method,
|
|
84
|
+
path: req.path,
|
|
85
|
+
query: req.query,
|
|
86
|
+
responseTime,
|
|
87
|
+
};
|
|
88
|
+
if (captureHeaders) {
|
|
89
|
+
metadata.headers = req.headers;
|
|
90
|
+
}
|
|
91
|
+
if (captureBody && req.body) {
|
|
92
|
+
metadata.body = req.body;
|
|
93
|
+
}
|
|
94
|
+
// Sanitize metadata if function provided
|
|
95
|
+
if (sanitize) {
|
|
96
|
+
metadata = sanitize(metadata);
|
|
97
|
+
}
|
|
98
|
+
// Determine auth phase: if a userId is present, this is post-auth
|
|
99
|
+
const reqAny = req;
|
|
100
|
+
const userObj = reqAny.user;
|
|
101
|
+
const userId = (typeof reqAny.userId === 'string' ? reqAny.userId : null) ||
|
|
102
|
+
(typeof userObj?.id === 'string' ? userObj.id : null);
|
|
103
|
+
const sessionId = (typeof reqAny.sessionId === 'string' ? reqAny.sessionId : null) ||
|
|
104
|
+
(typeof req.headers['x-session-id'] === 'string' ? req.headers['x-session-id'] : null);
|
|
105
|
+
const authPhase = userId ? 'post_auth' : 'pre_auth';
|
|
106
|
+
// Determine action taken based on status code and event type
|
|
107
|
+
let actionTaken = 'monitored';
|
|
108
|
+
if (statusCode === 429) {
|
|
109
|
+
actionTaken = 'rate_limited';
|
|
110
|
+
}
|
|
111
|
+
else if (statusCode === 403) {
|
|
112
|
+
actionTaken = 'blocked';
|
|
113
|
+
}
|
|
114
|
+
else if (statusCode === 401 && userId) {
|
|
115
|
+
actionTaken = 'session_revoked';
|
|
116
|
+
}
|
|
117
|
+
else if (statusCode === 401) {
|
|
118
|
+
actionTaken = 'blocked';
|
|
119
|
+
}
|
|
120
|
+
else if (statusCode >= 400 && statusCode < 500) {
|
|
121
|
+
actionTaken = 'flagged';
|
|
122
|
+
}
|
|
123
|
+
else if (statusCode >= 200 && statusCode < 300 && suspiciousPatterns.length > 0) {
|
|
124
|
+
actionTaken = 'flagged';
|
|
125
|
+
}
|
|
126
|
+
else if (statusCode >= 200 && statusCode < 300) {
|
|
127
|
+
actionTaken = 'allowed';
|
|
128
|
+
}
|
|
129
|
+
// Session remains active if action is not blocking/revoking
|
|
130
|
+
const sessionActiveAfterAction = !['blocked', 'session_revoked'].includes(actionTaken);
|
|
131
|
+
const event = {
|
|
132
|
+
eventType,
|
|
133
|
+
ip: getClientIp(req),
|
|
134
|
+
method: req.method,
|
|
135
|
+
path: req.path,
|
|
136
|
+
userAgent: req.get('user-agent') || null,
|
|
137
|
+
statusCode,
|
|
138
|
+
responseTime,
|
|
139
|
+
metadata: { ...metadata },
|
|
140
|
+
severity,
|
|
141
|
+
reason,
|
|
142
|
+
// v3.1 observability fields
|
|
143
|
+
clMode: mode,
|
|
144
|
+
authPhase,
|
|
145
|
+
actionTaken,
|
|
146
|
+
responseLatencyMs: responseTime,
|
|
147
|
+
userId: userId || null,
|
|
148
|
+
sessionId: sessionId || null,
|
|
149
|
+
sessionActiveAfterAction,
|
|
150
|
+
};
|
|
151
|
+
// Track event asynchronously (non-blocking)
|
|
152
|
+
client.trackEvent(event).catch(error => {
|
|
153
|
+
if (onError) {
|
|
154
|
+
onError(error);
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
if (onError) {
|
|
161
|
+
onError(error);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
next();
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Get client IP address from request
|
|
170
|
+
*/
|
|
171
|
+
function getClientIp(req) {
|
|
172
|
+
return (req.headers['x-forwarded-for']?.split(',')[0]?.trim() ||
|
|
173
|
+
req.headers['x-real-ip'] ||
|
|
174
|
+
req.socket.remoteAddress ||
|
|
175
|
+
'0.0.0.0');
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Detect suspicious patterns in request
|
|
179
|
+
*/
|
|
180
|
+
function detectSuspiciousPatterns(req) {
|
|
181
|
+
const patterns = [];
|
|
182
|
+
// SQL Injection patterns
|
|
183
|
+
const sqlPatterns = [
|
|
184
|
+
/('\s*\bor\b\s+'[^']*'\s*=\s*')/i,
|
|
185
|
+
/(\bunion\b\s+\bselect\b)/i,
|
|
186
|
+
/(\bdrop\b\s+\btable\b)/i,
|
|
187
|
+
/(\binsert\b\s+\binto\b)/i,
|
|
188
|
+
/('\s*or\s*'1'\s*=\s*'1)/i,
|
|
189
|
+
/(;\s*--)/,
|
|
190
|
+
];
|
|
191
|
+
// XSS patterns
|
|
192
|
+
const xssPatterns = [
|
|
193
|
+
/<script[^>]*>[\s\S]*?<\/script>/i,
|
|
194
|
+
/<iframe[^>]*>[\s\S]*?<\/iframe>/i,
|
|
195
|
+
/javascript:/i,
|
|
196
|
+
/onerror\s*=\s*/i,
|
|
197
|
+
/onload\s*=\s*/i,
|
|
198
|
+
];
|
|
199
|
+
// Path traversal patterns
|
|
200
|
+
const traversalPatterns = [
|
|
201
|
+
/\.\.[\/\\]/,
|
|
202
|
+
/\/etc\/passwd/i,
|
|
203
|
+
/\/windows\/system32/i,
|
|
204
|
+
];
|
|
205
|
+
let requestString;
|
|
206
|
+
try {
|
|
207
|
+
requestString = JSON.stringify({
|
|
208
|
+
url: req.url,
|
|
209
|
+
query: req.query,
|
|
210
|
+
body: req.body,
|
|
211
|
+
}).slice(0, 10000); // Limit size to prevent performance issues
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
requestString = req.url || '';
|
|
215
|
+
}
|
|
216
|
+
// Check SQL injection
|
|
217
|
+
if (sqlPatterns.some(pattern => pattern.test(requestString))) {
|
|
218
|
+
patterns.push('sql_injection');
|
|
219
|
+
}
|
|
220
|
+
// Check XSS
|
|
221
|
+
if (xssPatterns.some(pattern => pattern.test(requestString))) {
|
|
222
|
+
patterns.push('xss');
|
|
223
|
+
}
|
|
224
|
+
// Check path traversal
|
|
225
|
+
if (traversalPatterns.some(pattern => pattern.test(requestString))) {
|
|
226
|
+
patterns.push('path_traversal');
|
|
227
|
+
}
|
|
228
|
+
// Check for excessive failed auth attempts
|
|
229
|
+
if (req.headers['x-devfortress-failed-attempts']) {
|
|
230
|
+
const attempts = parseInt(req.headers['x-devfortress-failed-attempts'], 10);
|
|
231
|
+
if (attempts > 5) {
|
|
232
|
+
patterns.push('brute_force');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return patterns;
|
|
236
|
+
}
|
package/dist/quick.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DevFortress SDK — Zero-Config Quick Start
|
|
3
|
+
*
|
|
4
|
+
* One-line initialization for fastest time-to-first-event:
|
|
5
|
+
* import df from 'devfortress-sdk/quick';
|
|
6
|
+
* df.init({ apiKey: 'df_...' });
|
|
7
|
+
*
|
|
8
|
+
* Auto-detects framework (Express, Fastify, Next.js, Hono)
|
|
9
|
+
* and applies appropriate middleware configuration.
|
|
10
|
+
*/
|
|
11
|
+
import { DevFortress } from './devfortress';
|
|
12
|
+
export interface QuickConfig {
|
|
13
|
+
apiKey: string;
|
|
14
|
+
/** App identifier (auto-generated from package.json name if omitted) */
|
|
15
|
+
appId?: string;
|
|
16
|
+
/** Privacy mode: 'standard' | 'strict'. Strict never sends user agents, IPs are hashed */
|
|
17
|
+
privacy?: 'standard' | 'strict';
|
|
18
|
+
/** Enable console debug logs: [DF →] / [DF ✗] */
|
|
19
|
+
debug?: boolean;
|
|
20
|
+
/** Override ingest endpoint */
|
|
21
|
+
endpoint?: string;
|
|
22
|
+
/** Environment tag */
|
|
23
|
+
environment?: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function init(config: QuickConfig): DevFortress;
|
|
26
|
+
export declare function getInstance(): DevFortress | null;
|
|
27
|
+
/** Data snapshot for privacy transparency — shows what would be sent */
|
|
28
|
+
export declare function getDataSnapshot(req: unknown): {
|
|
29
|
+
collected: Record<string, string>;
|
|
30
|
+
neverCollected: string[];
|
|
31
|
+
};
|
|
32
|
+
declare const _default: {
|
|
33
|
+
init: typeof init;
|
|
34
|
+
getInstance: typeof getInstance;
|
|
35
|
+
getDataSnapshot: typeof getDataSnapshot;
|
|
36
|
+
};
|
|
37
|
+
export default _default;
|
package/dist/quick.js
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* DevFortress SDK — Zero-Config Quick Start
|
|
4
|
+
*
|
|
5
|
+
* One-line initialization for fastest time-to-first-event:
|
|
6
|
+
* import df from 'devfortress-sdk/quick';
|
|
7
|
+
* df.init({ apiKey: 'df_...' });
|
|
8
|
+
*
|
|
9
|
+
* Auto-detects framework (Express, Fastify, Next.js, Hono)
|
|
10
|
+
* and applies appropriate middleware configuration.
|
|
11
|
+
*/
|
|
12
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
13
|
+
exports.init = init;
|
|
14
|
+
exports.getInstance = getInstance;
|
|
15
|
+
exports.getDataSnapshot = getDataSnapshot;
|
|
16
|
+
const devfortress_1 = require("./devfortress");
|
|
17
|
+
function detectFramework() {
|
|
18
|
+
try {
|
|
19
|
+
// Check for Next.js
|
|
20
|
+
if (typeof process !== 'undefined') {
|
|
21
|
+
if (process.env.NEXT_RUNTIME || process.env.__NEXT_PRIVATE_ORIGIN) {
|
|
22
|
+
return 'nextjs';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Check for Express (most common)
|
|
26
|
+
try {
|
|
27
|
+
require.resolve('express');
|
|
28
|
+
return 'express';
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
/* not available */
|
|
32
|
+
}
|
|
33
|
+
// Check for Fastify
|
|
34
|
+
try {
|
|
35
|
+
require.resolve('fastify');
|
|
36
|
+
return 'fastify';
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
/* not available */
|
|
40
|
+
}
|
|
41
|
+
// Check for Hono
|
|
42
|
+
try {
|
|
43
|
+
require.resolve('hono');
|
|
44
|
+
return 'hono';
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
/* not available */
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
/* detection failed */
|
|
52
|
+
}
|
|
53
|
+
return 'unknown';
|
|
54
|
+
}
|
|
55
|
+
/* ─── Auto-detect app ID ─── */
|
|
56
|
+
function autoAppId() {
|
|
57
|
+
try {
|
|
58
|
+
// Try to read from nearest package.json
|
|
59
|
+
const pkg = require(process.cwd() + '/package.json');
|
|
60
|
+
if (pkg.name)
|
|
61
|
+
return pkg.name;
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
/* no package.json */
|
|
65
|
+
}
|
|
66
|
+
return `app-${Date.now().toString(36)}`;
|
|
67
|
+
}
|
|
68
|
+
/* ─── Quick start factory ─── */
|
|
69
|
+
let _instance = null;
|
|
70
|
+
function init(config) {
|
|
71
|
+
const framework = detectFramework();
|
|
72
|
+
const appId = config.appId ?? autoAppId();
|
|
73
|
+
const isStrict = config.privacy === 'strict';
|
|
74
|
+
const fullConfig = {
|
|
75
|
+
apiKey: config.apiKey,
|
|
76
|
+
appId,
|
|
77
|
+
environment: config.environment ?? process.env.NODE_ENV ?? 'production',
|
|
78
|
+
debug: config.debug ?? false,
|
|
79
|
+
endpoint: config.endpoint,
|
|
80
|
+
// In strict privacy mode, hash IPs before sending
|
|
81
|
+
enrichment: isStrict
|
|
82
|
+
? {
|
|
83
|
+
getIP: (req) => {
|
|
84
|
+
const r = req;
|
|
85
|
+
const raw = r.ip ?? r.socket?.remoteAddress ?? 'unknown';
|
|
86
|
+
// SHA-256 hash for privacy
|
|
87
|
+
const crypto = require('crypto');
|
|
88
|
+
return crypto
|
|
89
|
+
.createHash('sha256')
|
|
90
|
+
.update(raw)
|
|
91
|
+
.digest('hex')
|
|
92
|
+
.slice(0, 16);
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
: undefined,
|
|
96
|
+
};
|
|
97
|
+
_instance = new devfortress_1.DevFortress(fullConfig);
|
|
98
|
+
if (config.debug) {
|
|
99
|
+
console.log(`[DF →] DevFortress initialized (framework: ${framework}, privacy: ${config.privacy ?? 'standard'}, appId: ${appId})`);
|
|
100
|
+
}
|
|
101
|
+
return _instance;
|
|
102
|
+
}
|
|
103
|
+
function getInstance() {
|
|
104
|
+
return _instance;
|
|
105
|
+
}
|
|
106
|
+
/** Data snapshot for privacy transparency — shows what would be sent */
|
|
107
|
+
function getDataSnapshot(req) {
|
|
108
|
+
const r = req;
|
|
109
|
+
return {
|
|
110
|
+
collected: {
|
|
111
|
+
ip: typeof r.ip === 'string' ? r.ip : 'from socket',
|
|
112
|
+
method: String(r.method ?? 'unknown'),
|
|
113
|
+
path: String(r.url ?? r.path ?? 'unknown'),
|
|
114
|
+
statusCode: 'after response',
|
|
115
|
+
responseTime: 'measured automatically',
|
|
116
|
+
timestamp: new Date().toISOString(),
|
|
117
|
+
userAgent: 'first 200 chars only',
|
|
118
|
+
},
|
|
119
|
+
neverCollected: [
|
|
120
|
+
'Request body content',
|
|
121
|
+
'Response body content',
|
|
122
|
+
'Cookies or session data',
|
|
123
|
+
'Authorization headers',
|
|
124
|
+
'Real session tokens (anonymised via proprietary mechanism)',
|
|
125
|
+
'Query string values',
|
|
126
|
+
'Form data',
|
|
127
|
+
'File uploads',
|
|
128
|
+
'Database queries',
|
|
129
|
+
'Environment variables',
|
|
130
|
+
'Source code',
|
|
131
|
+
'User PII (names, emails, phone numbers)',
|
|
132
|
+
],
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
exports.default = { init, getInstance, getDataSnapshot };
|