devfortress-sdk 4.2.0 → 4.2.1
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/dist/abuseipdb.d.ts +10 -0
- package/dist/abuseipdb.js +121 -0
- package/dist/agent-security.d.ts +96 -0
- package/dist/agent-security.js +390 -0
- package/dist/agent.d.ts +61 -0
- package/dist/agent.js +177 -0
- package/dist/browser.d.ts +0 -27
- package/dist/browser.js +0 -33
- package/dist/circuit-breaker.d.ts +0 -41
- package/dist/circuit-breaker.js +1 -42
- package/dist/client.d.ts +0 -13
- package/dist/client.js +1 -19
- package/dist/devfortress.d.ts +64 -0
- package/dist/devfortress.js +758 -0
- package/dist/index.d.ts +0 -32
- package/dist/index.js +0 -40
- package/dist/internal-closed-loop-engine.d.ts +123 -0
- package/dist/internal-closed-loop-engine.js +683 -0
- package/dist/middleware/express.d.ts +0 -6
- package/dist/middleware/express.js +11 -41
- package/dist/quick.d.ts +0 -16
- package/dist/quick.js +0 -25
- package/dist/tier-gate.d.ts +38 -0
- package/dist/tier-gate.js +132 -0
- package/dist/token-alias.d.ts +47 -0
- package/dist/token-alias.js +312 -0
- package/dist/types.d.ts +0 -37
- package/dist/types.js +0 -10
- package/dist/unified-audit.d.ts +70 -0
- package/dist/unified-audit.js +171 -0
- package/package.json +2 -15
package/dist/agent.js
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AgentAdapter = void 0;
|
|
4
|
+
exports.validateAgentId = validateAgentId;
|
|
5
|
+
function summarizeInput(input, maxLen, sanitize) {
|
|
6
|
+
if (!input)
|
|
7
|
+
return '{}';
|
|
8
|
+
const keys = Object.keys(input);
|
|
9
|
+
const summary = {};
|
|
10
|
+
for (const key of keys) {
|
|
11
|
+
const val = input[key];
|
|
12
|
+
if (val === undefined || val === null) {
|
|
13
|
+
summary[key] = 'null';
|
|
14
|
+
}
|
|
15
|
+
else if (typeof val === 'string') {
|
|
16
|
+
if (sanitize &&
|
|
17
|
+
(key.toLowerCase().includes('key') ||
|
|
18
|
+
key.toLowerCase().includes('token') ||
|
|
19
|
+
key.toLowerCase().includes('secret') ||
|
|
20
|
+
key.toLowerCase().includes('password'))) {
|
|
21
|
+
summary[key] = '[REDACTED]';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
summary[key] = val.length > 64 ? val.substring(0, 61) + '...' : val;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
else if (typeof val === 'number' || typeof val === 'boolean') {
|
|
28
|
+
summary[key] = String(val);
|
|
29
|
+
}
|
|
30
|
+
else if (Array.isArray(val)) {
|
|
31
|
+
summary[key] = `Array(${val.length})`;
|
|
32
|
+
}
|
|
33
|
+
else {
|
|
34
|
+
summary[key] = '{...}';
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const str = JSON.stringify(summary);
|
|
38
|
+
return str.length > maxLen ? str.substring(0, maxLen - 3) + '...' : str;
|
|
39
|
+
}
|
|
40
|
+
const AGENT_ID_MAX_LENGTH = 128;
|
|
41
|
+
const AGENT_ID_PATTERN = /^[a-zA-Z0-9_\-.:@]+$/;
|
|
42
|
+
function validateAgentId(agentId) {
|
|
43
|
+
if (!agentId || typeof agentId !== 'string') {
|
|
44
|
+
throw new Error('agentId is required and must be a non-empty string');
|
|
45
|
+
}
|
|
46
|
+
if (agentId.length > AGENT_ID_MAX_LENGTH) {
|
|
47
|
+
throw new Error(`agentId must be at most ${AGENT_ID_MAX_LENGTH} characters`);
|
|
48
|
+
}
|
|
49
|
+
if (!AGENT_ID_PATTERN.test(agentId)) {
|
|
50
|
+
throw new Error('agentId may only contain alphanumeric characters, hyphens, underscores, dots, colons, and @');
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class AgentAdapter {
|
|
54
|
+
constructor(df, config) {
|
|
55
|
+
this.sessionToolCounts = new Map();
|
|
56
|
+
validateAgentId(config.agentId);
|
|
57
|
+
this.df = df;
|
|
58
|
+
this.agentId = config.agentId;
|
|
59
|
+
this.agentName = config.agentName || config.agentId;
|
|
60
|
+
this.agentIp = config.agentIp || '127.0.0.1';
|
|
61
|
+
this.maxInputSummaryLength = config.maxInputSummaryLength ?? 256;
|
|
62
|
+
this.sanitizeInputs = config.sanitizeInputs ?? true;
|
|
63
|
+
this.onFlagged = config.onFlagged;
|
|
64
|
+
}
|
|
65
|
+
async observeToolCall(toolCall, sessionId) {
|
|
66
|
+
const inputSummary = summarizeInput(toolCall.input, this.maxInputSummaryLength, this.sanitizeInputs);
|
|
67
|
+
const sid = sessionId || 'default';
|
|
68
|
+
const count = (this.sessionToolCounts.get(sid) || 0) + 1;
|
|
69
|
+
this.sessionToolCounts.set(sid, count);
|
|
70
|
+
const pseudoRequest = {
|
|
71
|
+
method: 'TOOL_CALL',
|
|
72
|
+
url: `/agent/${this.agentId}/tool/${toolCall.tool}`,
|
|
73
|
+
headers: {
|
|
74
|
+
'user-agent': `DevFortress-Agent/${this.agentName}`,
|
|
75
|
+
'x-forwarded-for': this.agentIp,
|
|
76
|
+
},
|
|
77
|
+
};
|
|
78
|
+
const result = await this.df.observe(pseudoRequest, {
|
|
79
|
+
meta: {
|
|
80
|
+
agent_id: this.agentId,
|
|
81
|
+
agent_name: this.agentName,
|
|
82
|
+
tool_name: toolCall.tool,
|
|
83
|
+
input_summary: inputSummary,
|
|
84
|
+
duration_ms: toolCall.durationMs,
|
|
85
|
+
model: toolCall.model,
|
|
86
|
+
framework: toolCall.framework || 'custom',
|
|
87
|
+
session_tool_count: count,
|
|
88
|
+
session_id: sessionId,
|
|
89
|
+
event_category: 'agent_tool_call',
|
|
90
|
+
},
|
|
91
|
+
skipEnrichment: true,
|
|
92
|
+
});
|
|
93
|
+
if (result?.flagged && this.onFlagged) {
|
|
94
|
+
await this.onFlagged(toolCall, result);
|
|
95
|
+
}
|
|
96
|
+
return result;
|
|
97
|
+
}
|
|
98
|
+
async observeLangChainTool(toolName, toolInput, options) {
|
|
99
|
+
return this.observeToolCall({
|
|
100
|
+
tool: toolName,
|
|
101
|
+
input: toolInput,
|
|
102
|
+
framework: 'langchain',
|
|
103
|
+
model: options?.model,
|
|
104
|
+
durationMs: options?.durationMs,
|
|
105
|
+
}, options?.sessionId);
|
|
106
|
+
}
|
|
107
|
+
async observeOpenAIToolCall(toolCall, options) {
|
|
108
|
+
let parsedArgs = {};
|
|
109
|
+
try {
|
|
110
|
+
parsedArgs = JSON.parse(toolCall.function.arguments);
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
parsedArgs = { raw: toolCall.function.arguments };
|
|
114
|
+
}
|
|
115
|
+
return this.observeToolCall({
|
|
116
|
+
tool: toolCall.function.name,
|
|
117
|
+
input: parsedArgs,
|
|
118
|
+
framework: 'openai',
|
|
119
|
+
model: options?.model,
|
|
120
|
+
durationMs: options?.durationMs,
|
|
121
|
+
}, options?.sessionId);
|
|
122
|
+
}
|
|
123
|
+
async observeHttpToolCall(url, method, options) {
|
|
124
|
+
return this.observeToolCall({
|
|
125
|
+
tool: `http:${method.toUpperCase()}`,
|
|
126
|
+
input: { url, status_code: options?.statusCode },
|
|
127
|
+
framework: 'raw_http',
|
|
128
|
+
durationMs: options?.durationMs,
|
|
129
|
+
meta: options?.meta,
|
|
130
|
+
}, options?.sessionId);
|
|
131
|
+
}
|
|
132
|
+
async trackSessionStart(sessionId, taskDescription) {
|
|
133
|
+
this.sessionToolCounts.set(sessionId, 0);
|
|
134
|
+
return this.df.observe({
|
|
135
|
+
method: 'SESSION_START',
|
|
136
|
+
url: `/agent/${this.agentId}/session/${sessionId}`,
|
|
137
|
+
headers: {
|
|
138
|
+
'user-agent': `DevFortress-Agent/${this.agentName}`,
|
|
139
|
+
'x-forwarded-for': this.agentIp,
|
|
140
|
+
},
|
|
141
|
+
}, {
|
|
142
|
+
meta: {
|
|
143
|
+
agent_id: this.agentId,
|
|
144
|
+
session_id: sessionId,
|
|
145
|
+
task_description: taskDescription,
|
|
146
|
+
event_category: 'agent_session_start',
|
|
147
|
+
},
|
|
148
|
+
skipEnrichment: true,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
async trackSessionEnd(sessionId, summary) {
|
|
152
|
+
const toolCount = this.sessionToolCounts.get(sessionId) || 0;
|
|
153
|
+
this.sessionToolCounts.delete(sessionId);
|
|
154
|
+
return this.df.observe({
|
|
155
|
+
method: 'SESSION_END',
|
|
156
|
+
url: `/agent/${this.agentId}/session/${sessionId}`,
|
|
157
|
+
headers: {
|
|
158
|
+
'user-agent': `DevFortress-Agent/${this.agentName}`,
|
|
159
|
+
'x-forwarded-for': this.agentIp,
|
|
160
|
+
},
|
|
161
|
+
}, {
|
|
162
|
+
meta: {
|
|
163
|
+
agent_id: this.agentId,
|
|
164
|
+
session_id: sessionId,
|
|
165
|
+
total_tools: summary?.totalTools ?? toolCount,
|
|
166
|
+
total_duration_ms: summary?.durationMs,
|
|
167
|
+
success: summary?.success,
|
|
168
|
+
event_category: 'agent_session_end',
|
|
169
|
+
},
|
|
170
|
+
skipEnrichment: true,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
getSessionToolCount(sessionId) {
|
|
174
|
+
return this.sessionToolCounts.get(sessionId) || 0;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
exports.AgentAdapter = AgentAdapter;
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DevFortress SDK - Browser Entry Point
|
|
3
|
-
*
|
|
4
|
-
* Lightweight browser-compatible client for sending security events
|
|
5
|
-
* from client-side applications. Uses fetch API instead of axios.
|
|
6
|
-
*
|
|
7
|
-
* @packageDocumentation
|
|
8
|
-
*/
|
|
9
1
|
export type { DevFortressClientOptions, LiveThreatEvent, EventType, SeverityLevel, ApiResponse, } from './types';
|
|
10
2
|
import type { DevFortressClientOptions } from './types';
|
|
11
|
-
/** @deprecated Use DevFortressClientOptions instead */
|
|
12
3
|
export type BrowserClientOptions = DevFortressClientOptions;
|
|
13
4
|
export declare class DevFortressBrowserClient {
|
|
14
5
|
private apiKey;
|
|
@@ -17,9 +8,6 @@ export declare class DevFortressBrowserClient {
|
|
|
17
8
|
private retries;
|
|
18
9
|
private debug;
|
|
19
10
|
constructor(options: DevFortressClientOptions);
|
|
20
|
-
/**
|
|
21
|
-
* Track a security event from the browser
|
|
22
|
-
*/
|
|
23
11
|
trackEvent(event: {
|
|
24
12
|
eventType: string;
|
|
25
13
|
ip?: string;
|
|
@@ -37,25 +25,10 @@ export declare class DevFortressBrowserClient {
|
|
|
37
25
|
eventId?: string;
|
|
38
26
|
message?: string;
|
|
39
27
|
}>;
|
|
40
|
-
/**
|
|
41
|
-
* Track a page error as a security event
|
|
42
|
-
*/
|
|
43
28
|
trackError(error: Error, context?: Record<string, unknown>): void;
|
|
44
|
-
/**
|
|
45
|
-
* Track a failed API response
|
|
46
|
-
*/
|
|
47
29
|
trackApiFailure(url: string, status: number, method?: string): void;
|
|
48
|
-
/**
|
|
49
|
-
* Install global error handler for automatic tracking
|
|
50
|
-
*/
|
|
51
30
|
installGlobalErrorHandler(): () => void;
|
|
52
|
-
/**
|
|
53
|
-
* Test connection to the surveillance API
|
|
54
|
-
*/
|
|
55
31
|
testConnection(): Promise<boolean>;
|
|
56
32
|
private sendWithRetry;
|
|
57
33
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Create a pre-configured browser client instance
|
|
60
|
-
*/
|
|
61
34
|
export declare function createBrowserClient(options: DevFortressClientOptions): DevFortressBrowserClient;
|
package/dist/browser.js
CHANGED
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* DevFortress SDK - Browser Entry Point
|
|
4
|
-
*
|
|
5
|
-
* Lightweight browser-compatible client for sending security events
|
|
6
|
-
* from client-side applications. Uses fetch API instead of axios.
|
|
7
|
-
*
|
|
8
|
-
* @packageDocumentation
|
|
9
|
-
*/
|
|
10
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
3
|
exports.DevFortressBrowserClient = void 0;
|
|
12
4
|
exports.createBrowserClient = createBrowserClient;
|
|
@@ -23,17 +15,12 @@ class DevFortressBrowserClient {
|
|
|
23
15
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
24
16
|
this.retries = options.retries || DEFAULT_RETRIES;
|
|
25
17
|
this.debug = options.debug || false;
|
|
26
|
-
// Warn if endpoint is not HTTPS
|
|
27
18
|
if (this.endpoint.startsWith('http://') &&
|
|
28
19
|
!this.endpoint.includes('localhost') &&
|
|
29
20
|
!this.endpoint.includes('127.0.0.1')) {
|
|
30
|
-
// eslint-disable-next-line no-console
|
|
31
21
|
console.warn('[DevFortress] WARNING: Using non-HTTPS endpoint. API key will be transmitted in cleartext.');
|
|
32
22
|
}
|
|
33
23
|
}
|
|
34
|
-
/**
|
|
35
|
-
* Track a security event from the browser
|
|
36
|
-
*/
|
|
37
24
|
async trackEvent(event) {
|
|
38
25
|
const payload = {
|
|
39
26
|
...event,
|
|
@@ -44,9 +31,6 @@ class DevFortressBrowserClient {
|
|
|
44
31
|
};
|
|
45
32
|
return this.sendWithRetry(payload, 1);
|
|
46
33
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Track a page error as a security event
|
|
49
|
-
*/
|
|
50
34
|
trackError(error, context) {
|
|
51
35
|
this.trackEvent({
|
|
52
36
|
eventType: 'custom',
|
|
@@ -57,12 +41,8 @@ class DevFortressBrowserClient {
|
|
|
57
41
|
},
|
|
58
42
|
severity: 'MEDIUM',
|
|
59
43
|
}).catch(() => {
|
|
60
|
-
// Silently fail - don't crash the app for telemetry
|
|
61
44
|
});
|
|
62
45
|
}
|
|
63
|
-
/**
|
|
64
|
-
* Track a failed API response
|
|
65
|
-
*/
|
|
66
46
|
trackApiFailure(url, status, method = 'GET') {
|
|
67
47
|
let eventType = 'custom';
|
|
68
48
|
let severity = 'LOW';
|
|
@@ -90,12 +70,8 @@ class DevFortressBrowserClient {
|
|
|
90
70
|
severity,
|
|
91
71
|
reason: `API ${method} ${url} returned ${status}`,
|
|
92
72
|
}).catch(() => {
|
|
93
|
-
// Silently fail
|
|
94
73
|
});
|
|
95
74
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Install global error handler for automatic tracking
|
|
98
|
-
*/
|
|
99
75
|
installGlobalErrorHandler() {
|
|
100
76
|
if (typeof window === 'undefined')
|
|
101
77
|
return () => { };
|
|
@@ -114,15 +90,11 @@ class DevFortressBrowserClient {
|
|
|
114
90
|
};
|
|
115
91
|
window.addEventListener('error', handler);
|
|
116
92
|
window.addEventListener('unhandledrejection', rejectionHandler);
|
|
117
|
-
// Return cleanup function
|
|
118
93
|
return () => {
|
|
119
94
|
window.removeEventListener('error', handler);
|
|
120
95
|
window.removeEventListener('unhandledrejection', rejectionHandler);
|
|
121
96
|
};
|
|
122
97
|
}
|
|
123
|
-
/**
|
|
124
|
-
* Test connection to the surveillance API
|
|
125
|
-
*/
|
|
126
98
|
async testConnection() {
|
|
127
99
|
try {
|
|
128
100
|
await this.trackEvent({
|
|
@@ -151,7 +123,6 @@ class DevFortressBrowserClient {
|
|
|
151
123
|
});
|
|
152
124
|
clearTimeout(timeoutId);
|
|
153
125
|
if (!response.ok) {
|
|
154
|
-
// Don't retry on client errors
|
|
155
126
|
if (response.status < 500) {
|
|
156
127
|
const errorData = await response.json().catch(() => ({}));
|
|
157
128
|
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
@@ -165,7 +136,6 @@ class DevFortressBrowserClient {
|
|
|
165
136
|
if (attempt < this.retries) {
|
|
166
137
|
const backoff = Math.pow(2, attempt) * 500;
|
|
167
138
|
if (this.debug) {
|
|
168
|
-
// eslint-disable-next-line no-console
|
|
169
139
|
console.log(`[DevFortress] Retrying in ${backoff}ms (attempt ${attempt}/${this.retries})`);
|
|
170
140
|
}
|
|
171
141
|
await new Promise(resolve => setTimeout(resolve, backoff));
|
|
@@ -176,9 +146,6 @@ class DevFortressBrowserClient {
|
|
|
176
146
|
}
|
|
177
147
|
}
|
|
178
148
|
exports.DevFortressBrowserClient = DevFortressBrowserClient;
|
|
179
|
-
/**
|
|
180
|
-
* Create a pre-configured browser client instance
|
|
181
|
-
*/
|
|
182
149
|
function createBrowserClient(options) {
|
|
183
150
|
return new DevFortressBrowserClient(options);
|
|
184
151
|
}
|
|
@@ -1,29 +1,7 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DevFortress SDK — Platform Circuit Breaker
|
|
3
|
-
*
|
|
4
|
-
* Monitors connectivity to the DevFortress platform and provides
|
|
5
|
-
* automatic failover from external to internal closed-loop mode.
|
|
6
|
-
*
|
|
7
|
-
* Three states:
|
|
8
|
-
* - CLOSED: Platform is reachable, external CL is active
|
|
9
|
-
* - OPEN: Platform is unreachable, internal CL takes over
|
|
10
|
-
* - HALF-OPEN: Testing if platform has recovered
|
|
11
|
-
*
|
|
12
|
-
* Transition rules:
|
|
13
|
-
* CLOSED → OPEN: After `failureThreshold` consecutive failures
|
|
14
|
-
* OPEN → HALF-OPEN: After `recoveryTimeMs` elapsed
|
|
15
|
-
* HALF-OPEN → CLOSED: First successful call
|
|
16
|
-
* HALF-OPEN → OPEN: First failed call
|
|
17
|
-
*
|
|
18
|
-
* @packageDocumentation
|
|
19
|
-
*/
|
|
20
1
|
export type CircuitState = 'closed' | 'open' | 'half-open';
|
|
21
2
|
export interface CircuitBreakerConfig {
|
|
22
|
-
/** Number of consecutive failures before opening the circuit. Default: 3 */
|
|
23
3
|
failureThreshold?: number;
|
|
24
|
-
/** Time in ms to wait before testing recovery. Default: 60_000 (1 min) */
|
|
25
4
|
recoveryTimeMs?: number;
|
|
26
|
-
/** Callback when circuit state changes */
|
|
27
5
|
onStateChange?: (from: CircuitState, to: CircuitState, reason: string) => void;
|
|
28
6
|
}
|
|
29
7
|
export declare class PlatformCircuitBreaker {
|
|
@@ -34,35 +12,16 @@ export declare class PlatformCircuitBreaker {
|
|
|
34
12
|
private recoveryTimeMs;
|
|
35
13
|
private onStateChange?;
|
|
36
14
|
constructor(config?: CircuitBreakerConfig);
|
|
37
|
-
/**
|
|
38
|
-
* Check if external platform calls should be attempted.
|
|
39
|
-
* Returns true if the circuit allows external calls (CLOSED or HALF-OPEN).
|
|
40
|
-
*/
|
|
41
15
|
shouldCallExternal(): boolean;
|
|
42
|
-
/**
|
|
43
|
-
* Record a successful external call.
|
|
44
|
-
*/
|
|
45
16
|
recordSuccess(): void;
|
|
46
|
-
/**
|
|
47
|
-
* Record a failed external call.
|
|
48
|
-
*/
|
|
49
17
|
recordFailure(): void;
|
|
50
|
-
/**
|
|
51
|
-
* Get current circuit state.
|
|
52
|
-
*/
|
|
53
18
|
getState(): CircuitState;
|
|
54
|
-
/**
|
|
55
|
-
* Get diagnostics info.
|
|
56
|
-
*/
|
|
57
19
|
getDiagnostics(): {
|
|
58
20
|
state: CircuitState;
|
|
59
21
|
failureCount: number;
|
|
60
22
|
lastFailureTime: number;
|
|
61
23
|
msUntilRecoveryAttempt: number;
|
|
62
24
|
};
|
|
63
|
-
/**
|
|
64
|
-
* Force reset the circuit breaker to closed state.
|
|
65
|
-
*/
|
|
66
25
|
reset(): void;
|
|
67
26
|
private transition;
|
|
68
27
|
}
|
package/dist/circuit-breaker.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* DevFortress SDK — Platform Circuit Breaker
|
|
4
|
-
*
|
|
5
|
-
* Monitors connectivity to the DevFortress platform and provides
|
|
6
|
-
* automatic failover from external to internal closed-loop mode.
|
|
7
|
-
*
|
|
8
|
-
* Three states:
|
|
9
|
-
* - CLOSED: Platform is reachable, external CL is active
|
|
10
|
-
* - OPEN: Platform is unreachable, internal CL takes over
|
|
11
|
-
* - HALF-OPEN: Testing if platform has recovered
|
|
12
|
-
*
|
|
13
|
-
* Transition rules:
|
|
14
|
-
* CLOSED → OPEN: After `failureThreshold` consecutive failures
|
|
15
|
-
* OPEN → HALF-OPEN: After `recoveryTimeMs` elapsed
|
|
16
|
-
* HALF-OPEN → CLOSED: First successful call
|
|
17
|
-
* HALF-OPEN → OPEN: First failed call
|
|
18
|
-
*
|
|
19
|
-
* @packageDocumentation
|
|
20
|
-
*/
|
|
21
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
22
3
|
exports.PlatformCircuitBreaker = void 0;
|
|
23
4
|
class PlatformCircuitBreaker {
|
|
@@ -29,36 +10,24 @@ class PlatformCircuitBreaker {
|
|
|
29
10
|
this.recoveryTimeMs = config.recoveryTimeMs ?? 60000;
|
|
30
11
|
this.onStateChange = config.onStateChange;
|
|
31
12
|
}
|
|
32
|
-
/**
|
|
33
|
-
* Check if external platform calls should be attempted.
|
|
34
|
-
* Returns true if the circuit allows external calls (CLOSED or HALF-OPEN).
|
|
35
|
-
*/
|
|
36
13
|
shouldCallExternal() {
|
|
37
14
|
if (this.state === 'closed')
|
|
38
15
|
return true;
|
|
39
16
|
if (this.state === 'open') {
|
|
40
|
-
// Check if recovery time has elapsed
|
|
41
17
|
if (Date.now() - this.lastFailureTime >= this.recoveryTimeMs) {
|
|
42
18
|
this.transition('half-open', 'recovery_timeout_elapsed');
|
|
43
|
-
return true;
|
|
19
|
+
return true;
|
|
44
20
|
}
|
|
45
21
|
return false;
|
|
46
22
|
}
|
|
47
|
-
// half-open — allow the test call
|
|
48
23
|
return true;
|
|
49
24
|
}
|
|
50
|
-
/**
|
|
51
|
-
* Record a successful external call.
|
|
52
|
-
*/
|
|
53
25
|
recordSuccess() {
|
|
54
26
|
if (this.state === 'half-open') {
|
|
55
27
|
this.transition('closed', 'test_call_succeeded');
|
|
56
28
|
}
|
|
57
29
|
this.failureCount = 0;
|
|
58
30
|
}
|
|
59
|
-
/**
|
|
60
|
-
* Record a failed external call.
|
|
61
|
-
*/
|
|
62
31
|
recordFailure() {
|
|
63
32
|
this.failureCount++;
|
|
64
33
|
this.lastFailureTime = Date.now();
|
|
@@ -70,15 +39,9 @@ class PlatformCircuitBreaker {
|
|
|
70
39
|
this.transition('open', `${this.failureCount}_consecutive_failures`);
|
|
71
40
|
}
|
|
72
41
|
}
|
|
73
|
-
/**
|
|
74
|
-
* Get current circuit state.
|
|
75
|
-
*/
|
|
76
42
|
getState() {
|
|
77
43
|
return this.state;
|
|
78
44
|
}
|
|
79
|
-
/**
|
|
80
|
-
* Get diagnostics info.
|
|
81
|
-
*/
|
|
82
45
|
getDiagnostics() {
|
|
83
46
|
const msUntilRecovery = this.state === 'open'
|
|
84
47
|
? Math.max(0, this.recoveryTimeMs - (Date.now() - this.lastFailureTime))
|
|
@@ -90,9 +53,6 @@ class PlatformCircuitBreaker {
|
|
|
90
53
|
msUntilRecoveryAttempt: msUntilRecovery,
|
|
91
54
|
};
|
|
92
55
|
}
|
|
93
|
-
/**
|
|
94
|
-
* Force reset the circuit breaker to closed state.
|
|
95
|
-
*/
|
|
96
56
|
reset() {
|
|
97
57
|
this.transition('closed', 'manual_reset');
|
|
98
58
|
this.failureCount = 0;
|
|
@@ -108,7 +68,6 @@ class PlatformCircuitBreaker {
|
|
|
108
68
|
this.onStateChange(from, to, reason);
|
|
109
69
|
}
|
|
110
70
|
catch {
|
|
111
|
-
// Callback errors should not break the circuit breaker
|
|
112
71
|
}
|
|
113
72
|
}
|
|
114
73
|
}
|
package/dist/client.d.ts
CHANGED
|
@@ -1,7 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* DevFortress API Client
|
|
3
|
-
* Core client for sending events to DevFortress surveillance API
|
|
4
|
-
*/
|
|
5
1
|
import type { DevFortressClientOptions, LiveThreatEvent, ApiResponse } from './types';
|
|
6
2
|
export declare class DevFortressClient {
|
|
7
3
|
private apiKey;
|
|
@@ -11,16 +7,7 @@ export declare class DevFortressClient {
|
|
|
11
7
|
private debug;
|
|
12
8
|
private axios;
|
|
13
9
|
constructor(options: DevFortressClientOptions);
|
|
14
|
-
/**
|
|
15
|
-
* Track a security event
|
|
16
|
-
*/
|
|
17
10
|
trackEvent(event: LiveThreatEvent): Promise<ApiResponse>;
|
|
18
|
-
/**
|
|
19
|
-
* Send event with automatic retry logic
|
|
20
|
-
*/
|
|
21
11
|
private sendWithRetry;
|
|
22
|
-
/**
|
|
23
|
-
* Test connection to surveillance API
|
|
24
|
-
*/
|
|
25
12
|
testConnection(): Promise<boolean>;
|
|
26
13
|
}
|
package/dist/client.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* DevFortress API Client
|
|
4
|
-
* Core client for sending events to DevFortress surveillance API
|
|
5
|
-
*/
|
|
6
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
7
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
8
4
|
};
|
|
@@ -22,11 +18,9 @@ class DevFortressClient {
|
|
|
22
18
|
this.timeout = options.timeout || DEFAULT_TIMEOUT;
|
|
23
19
|
this.retries = options.retries || DEFAULT_RETRIES;
|
|
24
20
|
this.debug = options.debug || false;
|
|
25
|
-
// Warn if endpoint is not HTTPS
|
|
26
21
|
if (this.endpoint.startsWith('http://') &&
|
|
27
22
|
!this.endpoint.includes('localhost') &&
|
|
28
23
|
!this.endpoint.includes('127.0.0.1')) {
|
|
29
|
-
// eslint-disable-next-line no-console
|
|
30
24
|
console.warn('[DevFortress] WARNING: Using non-HTTPS endpoint. API key will be transmitted in cleartext.');
|
|
31
25
|
}
|
|
32
26
|
this.axios = axios_1.default.create({
|
|
@@ -38,12 +32,8 @@ class DevFortressClient {
|
|
|
38
32
|
},
|
|
39
33
|
});
|
|
40
34
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Track a security event
|
|
43
|
-
*/
|
|
44
35
|
async trackEvent(event) {
|
|
45
36
|
try {
|
|
46
|
-
// Add timestamp if not provided
|
|
47
37
|
const payload = {
|
|
48
38
|
...event,
|
|
49
39
|
timestamp: event.timestamp || new Date().toISOString(),
|
|
@@ -55,9 +45,6 @@ class DevFortressClient {
|
|
|
55
45
|
throw new Error(`Failed to track event: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
|
56
46
|
}
|
|
57
47
|
}
|
|
58
|
-
/**
|
|
59
|
-
* Send event with automatic retry logic
|
|
60
|
-
*/
|
|
61
48
|
async sendWithRetry(payload, attempt = 1) {
|
|
62
49
|
try {
|
|
63
50
|
const response = await this.axios.post('', payload);
|
|
@@ -65,22 +52,17 @@ class DevFortressClient {
|
|
|
65
52
|
}
|
|
66
53
|
catch (error) {
|
|
67
54
|
const axiosError = error;
|
|
68
|
-
// Don't retry on client errors (4xx)
|
|
69
55
|
if (axiosError.response?.status && axiosError.response.status < 500) {
|
|
70
56
|
throw error;
|
|
71
57
|
}
|
|
72
|
-
// Retry on server errors (5xx) or network errors
|
|
73
58
|
if (attempt < this.retries) {
|
|
74
|
-
const backoff = Math.pow(2, attempt) * 1000;
|
|
59
|
+
const backoff = Math.pow(2, attempt) * 1000;
|
|
75
60
|
await new Promise(resolve => setTimeout(resolve, backoff));
|
|
76
61
|
return this.sendWithRetry(payload, attempt + 1);
|
|
77
62
|
}
|
|
78
63
|
throw error;
|
|
79
64
|
}
|
|
80
65
|
}
|
|
81
|
-
/**
|
|
82
|
-
* Test connection to surveillance API
|
|
83
|
-
*/
|
|
84
66
|
async testConnection() {
|
|
85
67
|
try {
|
|
86
68
|
await this.trackEvent({
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { DevFortressConfig, CLMode, ObserveResult, ObserveOptions, ThreatSeverity, ThreatHandler, ActionReport } from './types';
|
|
2
|
+
import { InternalClosedLoopEngine } from './internal-closed-loop-engine';
|
|
3
|
+
import { PlatformCircuitBreaker } from './circuit-breaker';
|
|
4
|
+
import { UnifiedAuditTrail } from './unified-audit';
|
|
5
|
+
import { TierGate } from './tier-gate';
|
|
6
|
+
export declare class DevFortress {
|
|
7
|
+
private apiKey;
|
|
8
|
+
private appId;
|
|
9
|
+
private environment;
|
|
10
|
+
private debug;
|
|
11
|
+
private endpoint;
|
|
12
|
+
private timeout;
|
|
13
|
+
private retries;
|
|
14
|
+
private enrichment;
|
|
15
|
+
private responseWebhook;
|
|
16
|
+
private cacheConfig;
|
|
17
|
+
private axios;
|
|
18
|
+
private apiAxios;
|
|
19
|
+
private handlers;
|
|
20
|
+
private mode;
|
|
21
|
+
private internalEngine;
|
|
22
|
+
private circuitBreaker;
|
|
23
|
+
private auditTrail;
|
|
24
|
+
private tierGate;
|
|
25
|
+
constructor(config: DevFortressConfig);
|
|
26
|
+
getMode(): CLMode;
|
|
27
|
+
getAudit(): UnifiedAuditTrail;
|
|
28
|
+
getTierGate(): TierGate;
|
|
29
|
+
getInternalEngine(): InternalClosedLoopEngine | null;
|
|
30
|
+
getCircuitBreakerDiagnostics(): ReturnType<PlatformCircuitBreaker['getDiagnostics']> | null;
|
|
31
|
+
observe(req: unknown, options?: ObserveOptions): Promise<ObserveResult | null>;
|
|
32
|
+
private observeExternal;
|
|
33
|
+
private observeInternal;
|
|
34
|
+
private observeHybrid;
|
|
35
|
+
private relayToExternalAsync;
|
|
36
|
+
private fireThreatHandlers;
|
|
37
|
+
enrichEvent(eventId: string, additionalData: Record<string, unknown>): Promise<void>;
|
|
38
|
+
isBlocked(ip: string, options?: {
|
|
39
|
+
userId?: string;
|
|
40
|
+
sessionId?: string;
|
|
41
|
+
}): Promise<boolean>;
|
|
42
|
+
onThreatDetected(severity: ThreatSeverity | '*', handler: ThreatHandler): void;
|
|
43
|
+
reportActionTaken(eventId: string, report: ActionReport): Promise<void>;
|
|
44
|
+
handleWebhook(rawBody: string, signature: string, timestamp: string): Promise<boolean>;
|
|
45
|
+
blockIP(ip: string, ttlSeconds?: number): void;
|
|
46
|
+
unblockIP(ip: string): void;
|
|
47
|
+
trackEvent(event: {
|
|
48
|
+
eventType: string;
|
|
49
|
+
ip: string;
|
|
50
|
+
severity?: 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
51
|
+
method?: string;
|
|
52
|
+
path?: string;
|
|
53
|
+
userAgent?: string;
|
|
54
|
+
statusCode?: number;
|
|
55
|
+
metadata?: Record<string, unknown>;
|
|
56
|
+
}): Promise<{
|
|
57
|
+
success: boolean;
|
|
58
|
+
eventId?: string;
|
|
59
|
+
}>;
|
|
60
|
+
registerTestIPs(ips: string[]): Promise<boolean>;
|
|
61
|
+
testConnection(): Promise<boolean>;
|
|
62
|
+
private sendWithRetry;
|
|
63
|
+
private log;
|
|
64
|
+
}
|