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
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TokenAliasManager = exports.EmergencyBlocklist = void 0;
|
|
7
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
8
|
+
const ENCRYPTED_PREFIX = 'enc:v1:';
|
|
9
|
+
function encrypt(plaintext, keyHex) {
|
|
10
|
+
const key = Buffer.from(keyHex, 'hex');
|
|
11
|
+
const iv = crypto_1.default.randomBytes(12);
|
|
12
|
+
const cipher = crypto_1.default.createCipheriv('aes-256-gcm', key, iv);
|
|
13
|
+
const encrypted = Buffer.concat([
|
|
14
|
+
cipher.update(plaintext, 'utf8'),
|
|
15
|
+
cipher.final(),
|
|
16
|
+
]);
|
|
17
|
+
const authTag = cipher.getAuthTag();
|
|
18
|
+
return (ENCRYPTED_PREFIX +
|
|
19
|
+
iv.toString('hex') +
|
|
20
|
+
':' +
|
|
21
|
+
encrypted.toString('hex') +
|
|
22
|
+
':' +
|
|
23
|
+
authTag.toString('hex'));
|
|
24
|
+
}
|
|
25
|
+
function decrypt(ciphertext, keyHex) {
|
|
26
|
+
if (!ciphertext.startsWith(ENCRYPTED_PREFIX)) {
|
|
27
|
+
return ciphertext;
|
|
28
|
+
}
|
|
29
|
+
const parts = ciphertext.slice(ENCRYPTED_PREFIX.length).split(':');
|
|
30
|
+
if (parts.length !== 3)
|
|
31
|
+
throw new Error('Invalid encrypted format');
|
|
32
|
+
const iv = Buffer.from(parts[0], 'hex');
|
|
33
|
+
const encrypted = Buffer.from(parts[1], 'hex');
|
|
34
|
+
const authTag = Buffer.from(parts[2], 'hex');
|
|
35
|
+
const key = Buffer.from(keyHex, 'hex');
|
|
36
|
+
const decipher = crypto_1.default.createDecipheriv('aes-256-gcm', key, iv);
|
|
37
|
+
decipher.setAuthTag(authTag);
|
|
38
|
+
return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString('utf8');
|
|
39
|
+
}
|
|
40
|
+
function hashToken(realToken) {
|
|
41
|
+
return crypto_1.default.createHash('sha256').update(realToken).digest('hex');
|
|
42
|
+
}
|
|
43
|
+
class LRUCache {
|
|
44
|
+
constructor(maxEntries = 1000, defaultTtlSeconds = 60) {
|
|
45
|
+
this.cache = new Map();
|
|
46
|
+
this.maxEntries = maxEntries;
|
|
47
|
+
this.defaultTtl = defaultTtlSeconds;
|
|
48
|
+
}
|
|
49
|
+
get(key) {
|
|
50
|
+
const entry = this.cache.get(key);
|
|
51
|
+
if (!entry)
|
|
52
|
+
return null;
|
|
53
|
+
if (Date.now() > entry.expiresAt) {
|
|
54
|
+
this.cache.delete(key);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
this.cache.delete(key);
|
|
58
|
+
this.cache.set(key, entry);
|
|
59
|
+
return entry.value;
|
|
60
|
+
}
|
|
61
|
+
set(key, value, ttlSeconds) {
|
|
62
|
+
if (this.cache.size >= this.maxEntries) {
|
|
63
|
+
const oldestKey = this.cache.keys().next().value;
|
|
64
|
+
if (oldestKey !== undefined)
|
|
65
|
+
this.cache.delete(oldestKey);
|
|
66
|
+
}
|
|
67
|
+
this.cache.set(key, {
|
|
68
|
+
value,
|
|
69
|
+
expiresAt: Date.now() + (ttlSeconds ?? this.defaultTtl) * 1000,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
del(key) {
|
|
73
|
+
this.cache.delete(key);
|
|
74
|
+
}
|
|
75
|
+
has(key) {
|
|
76
|
+
return this.get(key) !== null;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const memStore = new Map();
|
|
80
|
+
const memoryAdapter = {
|
|
81
|
+
async get(key) {
|
|
82
|
+
const entry = memStore.get(key);
|
|
83
|
+
if (!entry)
|
|
84
|
+
return null;
|
|
85
|
+
if (Date.now() > entry.expiresAt) {
|
|
86
|
+
memStore.delete(key);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
return entry.value;
|
|
90
|
+
},
|
|
91
|
+
async set(key, value, ttlSeconds) {
|
|
92
|
+
memStore.set(key, {
|
|
93
|
+
value,
|
|
94
|
+
expiresAt: Date.now() + ttlSeconds * 1000,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
async del(key) {
|
|
98
|
+
memStore.delete(key);
|
|
99
|
+
},
|
|
100
|
+
};
|
|
101
|
+
class EmergencyBlocklist {
|
|
102
|
+
constructor(cache, ttlSeconds = 900) {
|
|
103
|
+
this.cache = cache || memoryAdapter;
|
|
104
|
+
this.lru = new LRUCache(5000, ttlSeconds);
|
|
105
|
+
this.defaultTtlSeconds = ttlSeconds;
|
|
106
|
+
}
|
|
107
|
+
async blockIP(ip, reason = 'critical_event') {
|
|
108
|
+
const value = JSON.stringify({ reason, blockedAt: Date.now() });
|
|
109
|
+
this.lru.set(`emergency:block:${ip}`, value, this.defaultTtlSeconds);
|
|
110
|
+
try {
|
|
111
|
+
await this.cache.set(`emergency:block:${ip}`, value, this.defaultTtlSeconds);
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async isBlocked(ip) {
|
|
117
|
+
if (this.lru.has(`emergency:block:${ip}`))
|
|
118
|
+
return true;
|
|
119
|
+
try {
|
|
120
|
+
const val = await this.cache.get(`emergency:block:${ip}`);
|
|
121
|
+
return val !== null;
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
async unblockIP(ip) {
|
|
128
|
+
this.lru.del(`emergency:block:${ip}`);
|
|
129
|
+
try {
|
|
130
|
+
await this.cache.del(`emergency:block:${ip}`);
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
exports.EmergencyBlocklist = EmergencyBlocklist;
|
|
137
|
+
class TokenAliasManager {
|
|
138
|
+
constructor(cacheAdapter, config) {
|
|
139
|
+
this.cache = cacheAdapter || memoryAdapter;
|
|
140
|
+
this.encryptionKey =
|
|
141
|
+
config?.encryptionKey ||
|
|
142
|
+
process.env.DEVFORTRESS_REDIS_ENCRYPTION_KEY ||
|
|
143
|
+
null;
|
|
144
|
+
this.lru = new LRUCache(config?.lruMaxEntries ?? 1000, config?.lruTtlSeconds ?? 60);
|
|
145
|
+
this.rotationGracePeriod = config?.rotationGracePeriodSeconds ?? 1200;
|
|
146
|
+
this.onFallbackRevocation = config?.onFallbackRevocation;
|
|
147
|
+
if (this.encryptionKey && this.encryptionKey.length !== 64) {
|
|
148
|
+
throw new Error('DEVFORTRESS_REDIS_ENCRYPTION_KEY must be a 32-byte hex string (64 hex characters)');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
encryptValue(plaintext) {
|
|
152
|
+
if (!this.encryptionKey)
|
|
153
|
+
return plaintext;
|
|
154
|
+
return encrypt(plaintext, this.encryptionKey);
|
|
155
|
+
}
|
|
156
|
+
decryptValue(ciphertext) {
|
|
157
|
+
if (!this.encryptionKey)
|
|
158
|
+
return ciphertext;
|
|
159
|
+
return decrypt(ciphertext, this.encryptionKey);
|
|
160
|
+
}
|
|
161
|
+
async cacheGet(key) {
|
|
162
|
+
try {
|
|
163
|
+
const value = await this.cache.get(key);
|
|
164
|
+
if (value !== null) {
|
|
165
|
+
this.lru.set(key, value);
|
|
166
|
+
return value;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
}
|
|
171
|
+
return this.lru.get(key);
|
|
172
|
+
}
|
|
173
|
+
async cacheSet(key, value, ttlSeconds) {
|
|
174
|
+
this.lru.set(key, value, ttlSeconds);
|
|
175
|
+
try {
|
|
176
|
+
await this.cache.set(key, value, ttlSeconds);
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async cacheDel(key) {
|
|
182
|
+
this.lru.del(key);
|
|
183
|
+
try {
|
|
184
|
+
await this.cache.del(key);
|
|
185
|
+
}
|
|
186
|
+
catch {
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
async createAlias(realToken, userId, ttlSeconds = 3600) {
|
|
190
|
+
const tokenHash = hashToken(realToken);
|
|
191
|
+
const existingAlias = await this.cacheGet(`cred:alias:${tokenHash}`);
|
|
192
|
+
if (existingAlias)
|
|
193
|
+
return existingAlias;
|
|
194
|
+
const alias = 'df_sid_' + crypto_1.default.randomBytes(16).toString('hex');
|
|
195
|
+
const data = {
|
|
196
|
+
realToken,
|
|
197
|
+
userId,
|
|
198
|
+
createdAt: Date.now(),
|
|
199
|
+
};
|
|
200
|
+
const encryptedData = this.encryptValue(JSON.stringify(data));
|
|
201
|
+
await this.cacheSet(`alias:${alias}`, encryptedData, ttlSeconds);
|
|
202
|
+
await this.cacheSet(`cred:alias:${tokenHash}`, alias, ttlSeconds);
|
|
203
|
+
return alias;
|
|
204
|
+
}
|
|
205
|
+
async resolveAlias(alias) {
|
|
206
|
+
const raw = await this.cacheGet(`alias:${alias}`);
|
|
207
|
+
if (!raw)
|
|
208
|
+
return null;
|
|
209
|
+
try {
|
|
210
|
+
const decrypted = this.decryptValue(raw);
|
|
211
|
+
const data = JSON.parse(decrypted);
|
|
212
|
+
return {
|
|
213
|
+
realToken: data.realToken,
|
|
214
|
+
userId: data.userId,
|
|
215
|
+
createdAt: data.createdAt,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
async revokeByAlias(alias, reason = 'active_threat', subjectId) {
|
|
223
|
+
try {
|
|
224
|
+
const resolved = await this.resolveAlias(alias);
|
|
225
|
+
if (!resolved) {
|
|
226
|
+
if (subjectId && this.onFallbackRevocation) {
|
|
227
|
+
await this.onFallbackRevocation(subjectId, `[DF] alias resolution failed for ${alias} — falling back to subjectId revocation`);
|
|
228
|
+
}
|
|
229
|
+
return null;
|
|
230
|
+
}
|
|
231
|
+
const tokenHash = hashToken(resolved.realToken);
|
|
232
|
+
await this.cacheDel(`alias:${alias}`);
|
|
233
|
+
await this.cacheDel(`cred:alias:${tokenHash}`);
|
|
234
|
+
const revocationMarker = JSON.stringify({
|
|
235
|
+
revoked: true,
|
|
236
|
+
reason,
|
|
237
|
+
revokedAt: Date.now(),
|
|
238
|
+
revocation_scope: 'precise',
|
|
239
|
+
});
|
|
240
|
+
await this.cacheSet(`revoked:alias:${alias}`, revocationMarker, 86400);
|
|
241
|
+
return resolved;
|
|
242
|
+
}
|
|
243
|
+
catch (error) {
|
|
244
|
+
const fallbackSubject = subjectId || 'unknown';
|
|
245
|
+
if (this.onFallbackRevocation) {
|
|
246
|
+
await this.onFallbackRevocation(fallbackSubject, `[DF] cache error during revokeByAlias(${alias}) — falling back to subjectId revocation. Error: ${error instanceof Error ? error.message : String(error)}`);
|
|
247
|
+
}
|
|
248
|
+
try {
|
|
249
|
+
const revocationMarker = JSON.stringify({
|
|
250
|
+
revoked: true,
|
|
251
|
+
reason,
|
|
252
|
+
revokedAt: Date.now(),
|
|
253
|
+
revocation_scope: 'broad',
|
|
254
|
+
});
|
|
255
|
+
await this.cacheSet(`revoked:alias:${alias}`, revocationMarker, 86400);
|
|
256
|
+
}
|
|
257
|
+
catch {
|
|
258
|
+
}
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
async rotateAlias(oldAlias, ttlSeconds = 3600) {
|
|
263
|
+
const resolved = await this.resolveAlias(oldAlias);
|
|
264
|
+
if (!resolved)
|
|
265
|
+
return null;
|
|
266
|
+
const rotatingData = {
|
|
267
|
+
...resolved,
|
|
268
|
+
status: 'rotating',
|
|
269
|
+
canCreateNewEvents: false,
|
|
270
|
+
};
|
|
271
|
+
const encryptedRotating = this.encryptValue(JSON.stringify(rotatingData));
|
|
272
|
+
await this.cacheSet(`alias:${oldAlias}`, encryptedRotating, this.rotationGracePeriod);
|
|
273
|
+
const tokenHash = hashToken(resolved.realToken);
|
|
274
|
+
await this.cacheDel(`cred:alias:${tokenHash}`);
|
|
275
|
+
return this.createAlias(resolved.realToken, resolved.userId, ttlSeconds);
|
|
276
|
+
}
|
|
277
|
+
async revokeOnLogout(realToken) {
|
|
278
|
+
const tokenHash = hashToken(realToken);
|
|
279
|
+
const alias = await this.cacheGet(`cred:alias:${tokenHash}`);
|
|
280
|
+
if (!alias)
|
|
281
|
+
return;
|
|
282
|
+
const revocationMarker = JSON.stringify({
|
|
283
|
+
revoked: true,
|
|
284
|
+
reason: 'logout',
|
|
285
|
+
revokedAt: Date.now(),
|
|
286
|
+
revocation_scope: 'precise',
|
|
287
|
+
});
|
|
288
|
+
await this.cacheSet(`revoked:alias:${alias}`, revocationMarker, 86400);
|
|
289
|
+
await this.cacheDel(`alias:${alias}`);
|
|
290
|
+
await this.cacheDel(`cred:alias:${tokenHash}`);
|
|
291
|
+
}
|
|
292
|
+
async isRevoked(alias) {
|
|
293
|
+
const revoked = await this.cacheGet(`revoked:alias:${alias}`);
|
|
294
|
+
return revoked !== null;
|
|
295
|
+
}
|
|
296
|
+
async getRevocationDetails(alias) {
|
|
297
|
+
const raw = await this.cacheGet(`revoked:alias:${alias}`);
|
|
298
|
+
if (!raw)
|
|
299
|
+
return null;
|
|
300
|
+
try {
|
|
301
|
+
return JSON.parse(raw);
|
|
302
|
+
}
|
|
303
|
+
catch {
|
|
304
|
+
return {
|
|
305
|
+
reason: 'active_threat',
|
|
306
|
+
revokedAt: Date.now(),
|
|
307
|
+
revocation_scope: 'precise',
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
exports.TokenAliasManager = TokenAliasManager;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,28 +1,8 @@
|
|
|
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
1
|
export type CLMode = 'external' | 'internal' | 'hybrid';
|
|
17
2
|
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
3
|
export type SeverityLevel = 'LOW' | 'MEDIUM' | 'HIGH' | 'CRITICAL';
|
|
19
4
|
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
5
|
export type AuthPhase = 'pre_auth' | 'post_auth';
|
|
25
|
-
/** Action taken by the protection system in response to the threat */
|
|
26
6
|
export type ActionTaken = 'blocked' | 'rate_limited' | 'session_revoked' | 'captcha_challenged' | 'flagged' | 'allowed' | 'monitored';
|
|
27
7
|
export interface LiveThreatEvent {
|
|
28
8
|
eventType: EventType;
|
|
@@ -42,15 +22,10 @@ export interface LiveThreatEvent {
|
|
|
42
22
|
environment?: string;
|
|
43
23
|
compositeScore?: number;
|
|
44
24
|
abuseipdb?: AbuseIPDBScore;
|
|
45
|
-
/** Closed-loop protection mode active when event was captured */
|
|
46
25
|
clMode?: CLMode;
|
|
47
|
-
/** Whether event occurred pre-auth or post-auth */
|
|
48
26
|
authPhase?: AuthPhase;
|
|
49
|
-
/** Action taken by the protection system */
|
|
50
27
|
actionTaken?: ActionTaken;
|
|
51
|
-
/** Response latency in milliseconds (time from request to SDK event emission) */
|
|
52
28
|
responseLatencyMs?: number;
|
|
53
|
-
/** Whether user session remained active after the action was taken */
|
|
54
29
|
sessionActiveAfterAction?: boolean;
|
|
55
30
|
}
|
|
56
31
|
export interface DevFortressClientOptions {
|
|
@@ -79,30 +54,18 @@ export interface DevFortressConfig {
|
|
|
79
54
|
url?: string;
|
|
80
55
|
ttl?: number;
|
|
81
56
|
};
|
|
82
|
-
/** Closed-loop protection mode. Default: 'external' */
|
|
83
57
|
mode?: CLMode;
|
|
84
|
-
/** Subscription tier for feature gating */
|
|
85
58
|
tier?: 'starter' | 'pro' | 'enterprise';
|
|
86
|
-
/** Whether the hybrid add-on is enabled (Pro tier only) */
|
|
87
59
|
hybridAddonEnabled?: boolean;
|
|
88
|
-
/** Internal closed-loop engine configuration (for 'internal' or 'hybrid' mode) */
|
|
89
60
|
internalCL?: {
|
|
90
|
-
/** Fail-closed (block on error) or fail-open. Default: 'closed' */
|
|
91
61
|
failMode?: 'closed' | 'open';
|
|
92
|
-
/** Custom Tier 2 scorer */
|
|
93
62
|
tier2Scorer?: (request: unknown) => Promise<number> | number;
|
|
94
|
-
/** Score threshold for auto-block. Default: 85 */
|
|
95
63
|
blockThreshold?: number;
|
|
96
|
-
/** Rate limit: max requests per window. Default: 100 */
|
|
97
64
|
rateLimitMax?: number;
|
|
98
|
-
/** Rate limit window in ms. Default: 60000 */
|
|
99
65
|
rateLimitWindowMs?: number;
|
|
100
66
|
};
|
|
101
|
-
/** Circuit breaker config for hybrid mode fallback */
|
|
102
67
|
circuitBreaker?: {
|
|
103
|
-
/** Failures before opening circuit. Default: 3 */
|
|
104
68
|
failureThreshold?: number;
|
|
105
|
-
/** Time before testing recovery in ms. Default: 60000 */
|
|
106
69
|
recoveryTimeMs?: number;
|
|
107
70
|
};
|
|
108
71
|
}
|
package/dist/types.js
CHANGED
|
@@ -1,12 +1,2 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import type { ThreatSeverity } from './types';
|
|
2
|
+
import type { InternalCLDecision } from './internal-closed-loop-engine';
|
|
3
|
+
export type AuditSource = 'internal' | 'external' | 'hybrid';
|
|
4
|
+
export interface UnifiedAuditEntry {
|
|
5
|
+
eventId: string;
|
|
6
|
+
timestamp: number;
|
|
7
|
+
source: AuditSource;
|
|
8
|
+
tier: 0 | 1 | 2 | 3;
|
|
9
|
+
decision: InternalCLDecision | 'webhook_response';
|
|
10
|
+
score: number;
|
|
11
|
+
severity: ThreatSeverity;
|
|
12
|
+
latencyMs: number;
|
|
13
|
+
ip: string;
|
|
14
|
+
path: string;
|
|
15
|
+
method: string;
|
|
16
|
+
matchedRules: string[];
|
|
17
|
+
userId?: string | null;
|
|
18
|
+
agentId?: string;
|
|
19
|
+
sessionId?: string | null;
|
|
20
|
+
fallbackMode?: boolean;
|
|
21
|
+
meta?: Record<string, unknown>;
|
|
22
|
+
}
|
|
23
|
+
export interface UnifiedAuditConfig {
|
|
24
|
+
maxEntries?: number;
|
|
25
|
+
onEntry?: (entry: UnifiedAuditEntry) => void | Promise<void>;
|
|
26
|
+
debug?: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare class UnifiedAuditTrail {
|
|
29
|
+
private entries;
|
|
30
|
+
private maxEntries;
|
|
31
|
+
private onEntry?;
|
|
32
|
+
private debug;
|
|
33
|
+
private stats;
|
|
34
|
+
constructor(config?: UnifiedAuditConfig);
|
|
35
|
+
record(entry: UnifiedAuditEntry): void;
|
|
36
|
+
query(filters?: {
|
|
37
|
+
source?: AuditSource;
|
|
38
|
+
decision?: string;
|
|
39
|
+
ip?: string;
|
|
40
|
+
userId?: string;
|
|
41
|
+
agentId?: string;
|
|
42
|
+
minScore?: number;
|
|
43
|
+
since?: number;
|
|
44
|
+
limit?: number;
|
|
45
|
+
}): ReadonlyArray<UnifiedAuditEntry>;
|
|
46
|
+
getStats(): {
|
|
47
|
+
total: number;
|
|
48
|
+
internalDecisions: number;
|
|
49
|
+
externalDecisions: number;
|
|
50
|
+
hybridDecisions: number;
|
|
51
|
+
fallbackEvents: number;
|
|
52
|
+
totalBlocks: number;
|
|
53
|
+
totalAllows: number;
|
|
54
|
+
totalRateLimits: number;
|
|
55
|
+
avgInternalLatencyMs: number;
|
|
56
|
+
avgExternalLatencyMs: number;
|
|
57
|
+
};
|
|
58
|
+
getDecisionDistribution(): Record<string, number>;
|
|
59
|
+
getLatencyHistogram(): {
|
|
60
|
+
under1ms: number;
|
|
61
|
+
under5ms: number;
|
|
62
|
+
under50ms: number;
|
|
63
|
+
under200ms: number;
|
|
64
|
+
under1s: number;
|
|
65
|
+
over1s: number;
|
|
66
|
+
};
|
|
67
|
+
exportJSON(since?: number): string;
|
|
68
|
+
_reset(): void;
|
|
69
|
+
private updateStats;
|
|
70
|
+
}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UnifiedAuditTrail = void 0;
|
|
4
|
+
class UnifiedAuditTrail {
|
|
5
|
+
constructor(config = {}) {
|
|
6
|
+
this.entries = [];
|
|
7
|
+
this.stats = {
|
|
8
|
+
internalDecisions: 0,
|
|
9
|
+
externalDecisions: 0,
|
|
10
|
+
hybridDecisions: 0,
|
|
11
|
+
fallbackEvents: 0,
|
|
12
|
+
totalBlocks: 0,
|
|
13
|
+
totalAllows: 0,
|
|
14
|
+
totalRateLimits: 0,
|
|
15
|
+
avgInternalLatencyMs: 0,
|
|
16
|
+
avgExternalLatencyMs: 0,
|
|
17
|
+
internalLatencySum: 0,
|
|
18
|
+
externalLatencySum: 0,
|
|
19
|
+
};
|
|
20
|
+
this.maxEntries = config.maxEntries ?? 100000;
|
|
21
|
+
this.onEntry = config.onEntry;
|
|
22
|
+
this.debug = config.debug ?? false;
|
|
23
|
+
}
|
|
24
|
+
record(entry) {
|
|
25
|
+
if (this.entries.length >= this.maxEntries) {
|
|
26
|
+
this.entries.splice(0, Math.floor(this.maxEntries * 0.1));
|
|
27
|
+
}
|
|
28
|
+
this.entries.push(entry);
|
|
29
|
+
this.updateStats(entry);
|
|
30
|
+
if (this.onEntry) {
|
|
31
|
+
try {
|
|
32
|
+
const result = this.onEntry(entry);
|
|
33
|
+
if (result && typeof result.catch === 'function') {
|
|
34
|
+
result.catch(() => { });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
if (this.debug) {
|
|
41
|
+
console.log(`[DF Audit] ${entry.source}:tier${entry.tier} ${entry.decision} score=${entry.score} ${entry.ip} ${entry.method} ${entry.path} (${entry.latencyMs}ms)`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
query(filters) {
|
|
45
|
+
let result = this.entries;
|
|
46
|
+
if (filters) {
|
|
47
|
+
if (filters.source) {
|
|
48
|
+
result = result.filter(e => e.source === filters.source);
|
|
49
|
+
}
|
|
50
|
+
if (filters.decision) {
|
|
51
|
+
result = result.filter(e => e.decision === filters.decision);
|
|
52
|
+
}
|
|
53
|
+
if (filters.ip) {
|
|
54
|
+
result = result.filter(e => e.ip === filters.ip);
|
|
55
|
+
}
|
|
56
|
+
if (filters.userId) {
|
|
57
|
+
result = result.filter(e => e.userId === filters.userId);
|
|
58
|
+
}
|
|
59
|
+
if (filters.agentId) {
|
|
60
|
+
result = result.filter(e => e.agentId === filters.agentId);
|
|
61
|
+
}
|
|
62
|
+
if (filters.minScore !== undefined) {
|
|
63
|
+
result = result.filter(e => e.score >= filters.minScore);
|
|
64
|
+
}
|
|
65
|
+
if (filters.since) {
|
|
66
|
+
result = result.filter(e => e.timestamp >= filters.since);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const limit = filters?.limit ?? 100;
|
|
70
|
+
return result.slice(-limit);
|
|
71
|
+
}
|
|
72
|
+
getStats() {
|
|
73
|
+
return {
|
|
74
|
+
total: this.entries.length,
|
|
75
|
+
...this.stats,
|
|
76
|
+
avgInternalLatencyMs: this.stats.internalDecisions > 0
|
|
77
|
+
? Math.round(this.stats.internalLatencySum / this.stats.internalDecisions)
|
|
78
|
+
: 0,
|
|
79
|
+
avgExternalLatencyMs: this.stats.externalDecisions > 0
|
|
80
|
+
? Math.round(this.stats.externalLatencySum / this.stats.externalDecisions)
|
|
81
|
+
: 0,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
getDecisionDistribution() {
|
|
85
|
+
const dist = {};
|
|
86
|
+
for (const entry of this.entries) {
|
|
87
|
+
const key = `${entry.source}:${entry.decision}`;
|
|
88
|
+
dist[key] = (dist[key] || 0) + 1;
|
|
89
|
+
}
|
|
90
|
+
return dist;
|
|
91
|
+
}
|
|
92
|
+
getLatencyHistogram() {
|
|
93
|
+
const hist = {
|
|
94
|
+
under1ms: 0,
|
|
95
|
+
under5ms: 0,
|
|
96
|
+
under50ms: 0,
|
|
97
|
+
under200ms: 0,
|
|
98
|
+
under1s: 0,
|
|
99
|
+
over1s: 0,
|
|
100
|
+
};
|
|
101
|
+
for (const entry of this.entries) {
|
|
102
|
+
if (entry.latencyMs < 1)
|
|
103
|
+
hist.under1ms++;
|
|
104
|
+
else if (entry.latencyMs < 5)
|
|
105
|
+
hist.under5ms++;
|
|
106
|
+
else if (entry.latencyMs < 50)
|
|
107
|
+
hist.under50ms++;
|
|
108
|
+
else if (entry.latencyMs < 200)
|
|
109
|
+
hist.under200ms++;
|
|
110
|
+
else if (entry.latencyMs < 1000)
|
|
111
|
+
hist.under1s++;
|
|
112
|
+
else
|
|
113
|
+
hist.over1s++;
|
|
114
|
+
}
|
|
115
|
+
return hist;
|
|
116
|
+
}
|
|
117
|
+
exportJSON(since) {
|
|
118
|
+
const entries = since
|
|
119
|
+
? this.entries.filter(e => e.timestamp >= since)
|
|
120
|
+
: this.entries;
|
|
121
|
+
return JSON.stringify(entries, null, 2);
|
|
122
|
+
}
|
|
123
|
+
_reset() {
|
|
124
|
+
this.entries = [];
|
|
125
|
+
this.stats = {
|
|
126
|
+
internalDecisions: 0,
|
|
127
|
+
externalDecisions: 0,
|
|
128
|
+
hybridDecisions: 0,
|
|
129
|
+
fallbackEvents: 0,
|
|
130
|
+
totalBlocks: 0,
|
|
131
|
+
totalAllows: 0,
|
|
132
|
+
totalRateLimits: 0,
|
|
133
|
+
avgInternalLatencyMs: 0,
|
|
134
|
+
avgExternalLatencyMs: 0,
|
|
135
|
+
internalLatencySum: 0,
|
|
136
|
+
externalLatencySum: 0,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
updateStats(entry) {
|
|
140
|
+
if (entry.source === 'internal') {
|
|
141
|
+
this.stats.internalDecisions++;
|
|
142
|
+
this.stats.internalLatencySum += entry.latencyMs;
|
|
143
|
+
}
|
|
144
|
+
else if (entry.source === 'external') {
|
|
145
|
+
this.stats.externalDecisions++;
|
|
146
|
+
this.stats.externalLatencySum += entry.latencyMs;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
this.stats.hybridDecisions++;
|
|
150
|
+
if (entry.tier > 0) {
|
|
151
|
+
this.stats.internalLatencySum += entry.latencyMs;
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.stats.externalLatencySum += entry.latencyMs;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
if (entry.fallbackMode) {
|
|
158
|
+
this.stats.fallbackEvents++;
|
|
159
|
+
}
|
|
160
|
+
if (entry.decision === 'block') {
|
|
161
|
+
this.stats.totalBlocks++;
|
|
162
|
+
}
|
|
163
|
+
else if (entry.decision === 'allow') {
|
|
164
|
+
this.stats.totalAllows++;
|
|
165
|
+
}
|
|
166
|
+
else if (entry.decision === 'rate_limit') {
|
|
167
|
+
this.stats.totalRateLimits++;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
exports.UnifiedAuditTrail = UnifiedAuditTrail;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "devfortress-sdk",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.1",
|
|
4
4
|
"description": "DevFortress SDK — API and application security with automated threat response, session privacy, and AI agent observability.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"browser": "dist/browser.js",
|
|
@@ -27,20 +27,7 @@
|
|
|
27
27
|
"devfortress-init": "./bin/devfortress-init.js"
|
|
28
28
|
},
|
|
29
29
|
"files": [
|
|
30
|
-
"dist
|
|
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",
|
|
30
|
+
"dist",
|
|
44
31
|
"bin/devfortress-init.js",
|
|
45
32
|
"src/middleware/fastapi.py",
|
|
46
33
|
"src/middleware/flask.py",
|