agentshield-sdk 12.0.0 → 13.0.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/package.json +2 -2
- package/src/cross-turn.js +6 -5
- package/src/fleet-defense.js +483 -0
- package/src/hitl-guard.js +487 -0
- package/src/main.js +51 -0
- package/src/memory-guard.js +637 -0
- package/src/micro-model.js +4 -1
- package/src/semantic-guard.js +452 -0
- package/src/trap-defense.js +468 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentshield-sdk",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"description": "SOTA AI agent security SDK. F1 1.000 on BIPIA/HackAPrompt/MCPTox/Multilingual benchmarks. 400+ exports, 100+ modules. Zero dependencies, runs locally.",
|
|
5
5
|
"main": "src/main.js",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
},
|
|
24
24
|
"sideEffects": false,
|
|
25
25
|
"scripts": {
|
|
26
|
-
"test": "node test/test.js && node test/test-modules.js && node test/test-new-features.js && node test/test-mcp-guard.js && node test/test-supply-chain-scanner.js && node test/test-owasp-agentic.js && node test/test-redteam-cli.js && node test/test-drift-monitor.js && node test/test-micro-model.js && node test/test-level5.js && node test/test-sota.js && node test/test-cross-turn.js && node test/test-v12.js",
|
|
26
|
+
"test": "node test/test.js && node test/test-modules.js && node test/test-new-features.js && node test/test-mcp-guard.js && node test/test-supply-chain-scanner.js && node test/test-owasp-agentic.js && node test/test-redteam-cli.js && node test/test-drift-monitor.js && node test/test-micro-model.js && node test/test-level5.js && node test/test-sota.js && node test/test-cross-turn.js && node test/test-v12.js && node test/test-traps.js",
|
|
27
27
|
"test:new-products": "node test/test-mcp-guard.js && node test/test-supply-chain-scanner.js && node test/test-owasp-agentic.js && node test/test-redteam-cli.js && node test/test-drift-monitor.js && node test/test-micro-model.js",
|
|
28
28
|
"test:all": "node test/test-all-40-features.js",
|
|
29
29
|
"test:mcp": "node test/test-mcp-security.js",
|
package/src/cross-turn.js
CHANGED
|
@@ -79,14 +79,15 @@ class ConversationTracker {
|
|
|
79
79
|
* @returns {{ safe: boolean, alerts: Array<object>, turnAnalysis: object }}
|
|
80
80
|
*/
|
|
81
81
|
addTurn(role, content) {
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
82
|
+
const safeContent = (content && typeof content === 'string') ? content : '';
|
|
83
|
+
const threats = scanText(safeContent).threats || [];
|
|
84
|
+
const topic = this._classifyTopic(safeContent);
|
|
85
|
+
const escalationSignals = this._countEscalationSignals(safeContent);
|
|
86
|
+
const trustErosion = this._detectTrustErosion(safeContent);
|
|
86
87
|
|
|
87
88
|
const turn = {
|
|
88
89
|
role,
|
|
89
|
-
content:
|
|
90
|
+
content: safeContent.substring(0, 1000),
|
|
90
91
|
timestamp: Date.now(),
|
|
91
92
|
threats,
|
|
92
93
|
topic,
|
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — Systemic Trap Defenses (Trap 5)
|
|
5
|
+
*
|
|
6
|
+
* Based on DeepMind's "AI Agent Traps" paper, this module defends against
|
|
7
|
+
* systemic risks in multi-agent fleets: coordinated attacks, cascade
|
|
8
|
+
* failures, financial manipulation, and single points of failure.
|
|
9
|
+
*
|
|
10
|
+
* Four defense layers:
|
|
11
|
+
* 1. FleetCorrelationEngine — detects coordinated behavior changes
|
|
12
|
+
* 2. CascadeBreaker — tracks data lineage and quarantines compromised agents
|
|
13
|
+
* 3. FinancialContentValidator — flags unverified financial claims
|
|
14
|
+
* 4. DependencyDiversityScanner — maps single points of failure
|
|
15
|
+
*
|
|
16
|
+
* All detection runs locally — no data ever leaves your environment.
|
|
17
|
+
*
|
|
18
|
+
* @module fleet-defense
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
// =========================================================================
|
|
22
|
+
// CONSTANTS
|
|
23
|
+
// =========================================================================
|
|
24
|
+
|
|
25
|
+
/** Default correlation time window in milliseconds. */
|
|
26
|
+
const DEFAULT_CORRELATION_WINDOW_MS = 60_000;
|
|
27
|
+
|
|
28
|
+
/** Minimum agents for a correlated behavior alert. */
|
|
29
|
+
const DEFAULT_CORRELATION_THRESHOLD = 3;
|
|
30
|
+
|
|
31
|
+
/** Patterns for financial content detection. */
|
|
32
|
+
const FINANCIAL_PATTERNS = [
|
|
33
|
+
{ regex: /\$[\d,]+(?:\.\d{1,2})?/g, category: 'price', label: 'Dollar amount' },
|
|
34
|
+
{ regex: /(?:price|cost|fee|rate)\s*(?:is|was|of|:)\s*\$?[\d,]+/gi, category: 'price', label: 'Price statement' },
|
|
35
|
+
{ regex: /(?:revenue|earnings|profit|income|loss)\s*(?:of|is|was|:)\s*\$?[\d,]+/gi, category: 'earnings', label: 'Earnings claim' },
|
|
36
|
+
{ regex: /market\s*cap(?:italization)?\s*(?:of|is|was|:)\s*\$?[\d,]+/gi, category: 'market_cap', label: 'Market cap claim' },
|
|
37
|
+
{ regex: /(?:up|down|rose|fell|gained|lost|increased|decreased)\s+[\d.]+\s*%/gi, category: 'movement', label: 'Price movement' },
|
|
38
|
+
{ regex: /(?:buy|sell|trade|invest|short|long)\s+(?:[\d,]+\s+)?(?:shares?|stocks?|options?|contracts?)/gi, category: 'trade_instruction', label: 'Trading instruction' },
|
|
39
|
+
{ regex: /(?:transfer|send|wire|pay|remit)\s+\$?[\d,]+/gi, category: 'transfer', label: 'Transfer instruction' },
|
|
40
|
+
{ regex: /(?:dividend|yield|roi|return)\s*(?:of|is|was|:)\s*[\d.]+\s*%/gi, category: 'return', label: 'Return claim' }
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
/** Actions requiring human approval for financial operations. */
|
|
44
|
+
const FINANCIAL_APPROVAL_ACTIONS = [
|
|
45
|
+
'trade', 'trading', 'buy', 'sell', 'transfer', 'payment', 'pay',
|
|
46
|
+
'wire', 'withdraw', 'deposit', 'invest', 'short', 'liquidate'
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// =========================================================================
|
|
50
|
+
// 1. FleetCorrelationEngine
|
|
51
|
+
// =========================================================================
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Monitors all agents in a fleet for coordinated behavior changes.
|
|
55
|
+
* Detects when multiple agents simultaneously change behavior in the
|
|
56
|
+
* same direction, which may indicate a coordinated attack.
|
|
57
|
+
*/
|
|
58
|
+
class FleetCorrelationEngine {
|
|
59
|
+
/**
|
|
60
|
+
* @param {object} [options]
|
|
61
|
+
* @param {number} [options.windowMs=60000] - Time window for correlation detection
|
|
62
|
+
* @param {number} [options.threshold=3] - Minimum agents for a correlated alert
|
|
63
|
+
*/
|
|
64
|
+
constructor(options = {}) {
|
|
65
|
+
this._windowMs = options.windowMs || DEFAULT_CORRELATION_WINDOW_MS;
|
|
66
|
+
this._threshold = options.threshold || DEFAULT_CORRELATION_THRESHOLD;
|
|
67
|
+
/** @type {Array<{agentId: string, action: string, topic: string, timestamp: number, threat: string|null}>} */
|
|
68
|
+
this._events = [];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Record an event from an agent.
|
|
73
|
+
* @param {string} agentId - Agent identifier
|
|
74
|
+
* @param {{ action: string, topic?: string, timestamp?: number, threat?: string }} event
|
|
75
|
+
*/
|
|
76
|
+
recordAgentEvent(agentId, event) {
|
|
77
|
+
this._events.push({
|
|
78
|
+
agentId,
|
|
79
|
+
action: event.action,
|
|
80
|
+
topic: event.topic || '',
|
|
81
|
+
timestamp: event.timestamp || Date.now(),
|
|
82
|
+
threat: event.threat || null
|
|
83
|
+
});
|
|
84
|
+
if (this._events.length > 50000) this._events = this._events.slice(-50000);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Detect correlated behavior across agents within a time window.
|
|
89
|
+
* @param {number} [windowMs] - Override detection window
|
|
90
|
+
* @returns {{ correlated: boolean, agentCount: number, commonAction: string, timeWindow: number, severity: string }}
|
|
91
|
+
*/
|
|
92
|
+
detectCorrelation(windowMs) {
|
|
93
|
+
const window = windowMs || this._windowMs;
|
|
94
|
+
const now = Date.now();
|
|
95
|
+
const cutoff = now - window;
|
|
96
|
+
|
|
97
|
+
// Get recent events
|
|
98
|
+
const recent = this._events.filter(e => e.timestamp >= cutoff);
|
|
99
|
+
|
|
100
|
+
// Group by action
|
|
101
|
+
const actionGroups = {};
|
|
102
|
+
for (const event of recent) {
|
|
103
|
+
const key = event.action;
|
|
104
|
+
if (!actionGroups[key]) actionGroups[key] = new Set();
|
|
105
|
+
actionGroups[key].add(event.agentId);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Find the most common action
|
|
109
|
+
let maxAction = '';
|
|
110
|
+
let maxAgents = 0;
|
|
111
|
+
for (const [action, agents] of Object.entries(actionGroups)) {
|
|
112
|
+
if (agents.size > maxAgents) {
|
|
113
|
+
maxAgents = agents.size;
|
|
114
|
+
maxAction = action;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const correlated = maxAgents >= this._threshold;
|
|
119
|
+
let severity = 'low';
|
|
120
|
+
if (maxAgents >= this._threshold * 2) severity = 'critical';
|
|
121
|
+
else if (maxAgents >= this._threshold) severity = 'high';
|
|
122
|
+
|
|
123
|
+
if (correlated) {
|
|
124
|
+
console.log(`[Agent Shield] Fleet: Correlated behavior detected — ${maxAgents} agents performing "${maxAction}" within ${window}ms`);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
correlated,
|
|
129
|
+
agentCount: maxAgents,
|
|
130
|
+
commonAction: maxAction,
|
|
131
|
+
timeWindow: window,
|
|
132
|
+
severity
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get all recorded events.
|
|
138
|
+
* @returns {Array}
|
|
139
|
+
*/
|
|
140
|
+
getEvents() {
|
|
141
|
+
return [...this._events];
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** Clear all events. */
|
|
145
|
+
reset() {
|
|
146
|
+
this._events = [];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// =========================================================================
|
|
151
|
+
// 2. CascadeBreaker
|
|
152
|
+
// =========================================================================
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Tracks data lineage across agents and quarantines data from
|
|
156
|
+
* compromised agents to prevent cascade failures.
|
|
157
|
+
*/
|
|
158
|
+
class CascadeBreaker {
|
|
159
|
+
constructor() {
|
|
160
|
+
/** @type {Array<{fromAgent: string, toAgent: string, dataHash: string, timestamp: number}>} */
|
|
161
|
+
this._flows = [];
|
|
162
|
+
/** @type {Set<string>} */
|
|
163
|
+
this._compromised = new Set();
|
|
164
|
+
/** @type {Set<string>} Quarantined data hashes */
|
|
165
|
+
this._quarantined = new Set();
|
|
166
|
+
/** @type {Map<string, Array>} dataHash -> flows */
|
|
167
|
+
this._flowIndex = new Map();
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Register a data flow between agents.
|
|
172
|
+
* @param {string} fromAgent - Source agent ID
|
|
173
|
+
* @param {string} toAgent - Destination agent ID
|
|
174
|
+
* @param {string} dataHash - Hash identifying the data
|
|
175
|
+
*/
|
|
176
|
+
registerDataFlow(fromAgent, toAgent, dataHash) {
|
|
177
|
+
const flow = { fromAgent, toAgent, dataHash, timestamp: Date.now() };
|
|
178
|
+
this._flows.push(flow);
|
|
179
|
+
if (!this._flowIndex.has(dataHash)) this._flowIndex.set(dataHash, []);
|
|
180
|
+
this._flowIndex.get(dataHash).push(flow);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Mark an agent as compromised.
|
|
185
|
+
* @param {string} agentId - Agent to mark
|
|
186
|
+
*/
|
|
187
|
+
markCompromised(agentId) {
|
|
188
|
+
this._compromised.add(agentId);
|
|
189
|
+
console.log(`[Agent Shield] Fleet: Agent "${agentId}" marked as compromised`);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Check whether a data hash originates from a compromised agent.
|
|
194
|
+
* @param {string} dataHash - Data hash to check
|
|
195
|
+
* @returns {{ safe: boolean, originAgent: string|null, compromised: boolean }}
|
|
196
|
+
*/
|
|
197
|
+
checkData(dataHash) {
|
|
198
|
+
if (this._quarantined.has(dataHash)) {
|
|
199
|
+
return { safe: false, originAgent: null, compromised: true };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// Find origin
|
|
203
|
+
const flows = this._flowIndex.get(dataHash);
|
|
204
|
+
const flow = flows ? flows[0] : null;
|
|
205
|
+
if (!flow) {
|
|
206
|
+
return { safe: true, originAgent: null, compromised: false };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Trace back to original sender
|
|
210
|
+
let origin = flow.fromAgent;
|
|
211
|
+
const visited = new Set();
|
|
212
|
+
while (true) {
|
|
213
|
+
if (visited.has(origin)) break;
|
|
214
|
+
visited.add(origin);
|
|
215
|
+
const upstream = this._flows.find(f => f.toAgent === origin && f.dataHash === dataHash);
|
|
216
|
+
if (!upstream) break;
|
|
217
|
+
origin = upstream.fromAgent;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const compromised = this._compromised.has(origin);
|
|
221
|
+
return { safe: !compromised, originAgent: origin, compromised };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Quarantine all data from a compromised agent, blocking downstream propagation.
|
|
226
|
+
* @param {string} agentId - Compromised agent ID
|
|
227
|
+
* @returns {{ quarantinedHashes: string[], affectedAgents: string[] }}
|
|
228
|
+
*/
|
|
229
|
+
quarantineDownstream(agentId) {
|
|
230
|
+
const quarantinedHashes = [];
|
|
231
|
+
const affectedAgents = new Set();
|
|
232
|
+
|
|
233
|
+
// Find all data that originated from or passed through this agent
|
|
234
|
+
const agentHashes = new Set();
|
|
235
|
+
for (const flow of this._flows) {
|
|
236
|
+
if (flow.fromAgent === agentId) {
|
|
237
|
+
agentHashes.add(flow.dataHash);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Mark all downstream flows as quarantined
|
|
242
|
+
const queue = [...agentHashes];
|
|
243
|
+
while (queue.length > 0) {
|
|
244
|
+
const hash = queue.pop();
|
|
245
|
+
if (this._quarantined.has(hash)) continue;
|
|
246
|
+
this._quarantined.add(hash);
|
|
247
|
+
quarantinedHashes.push(hash);
|
|
248
|
+
|
|
249
|
+
// Find downstream agents that received this data
|
|
250
|
+
for (const flow of this._flows) {
|
|
251
|
+
if (flow.dataHash === hash) {
|
|
252
|
+
affectedAgents.add(flow.toAgent);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (quarantinedHashes.length > 0) {
|
|
258
|
+
console.log(`[Agent Shield] Fleet: Quarantined ${quarantinedHashes.length} data hash(es) from agent "${agentId}"`);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return {
|
|
262
|
+
quarantinedHashes,
|
|
263
|
+
affectedAgents: [...affectedAgents]
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get list of compromised agents.
|
|
269
|
+
* @returns {string[]}
|
|
270
|
+
*/
|
|
271
|
+
getCompromised() {
|
|
272
|
+
return [...this._compromised];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Reset all state. */
|
|
276
|
+
reset() {
|
|
277
|
+
this._flows = [];
|
|
278
|
+
this._compromised.clear();
|
|
279
|
+
this._quarantined.clear();
|
|
280
|
+
this._flowIndex.clear();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// =========================================================================
|
|
285
|
+
// 3. FinancialContentValidator
|
|
286
|
+
// =========================================================================
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Scans content for financial claims and flags actions requiring
|
|
290
|
+
* human approval for financial operations.
|
|
291
|
+
*/
|
|
292
|
+
class FinancialContentValidator {
|
|
293
|
+
/**
|
|
294
|
+
* @param {object} [options]
|
|
295
|
+
* @param {Array} [options.additionalPatterns] - Extra financial patterns
|
|
296
|
+
* @param {string[]} [options.approvalActions] - Actions requiring approval
|
|
297
|
+
*/
|
|
298
|
+
constructor(options = {}) {
|
|
299
|
+
this._patterns = [...FINANCIAL_PATTERNS, ...(options.additionalPatterns || [])];
|
|
300
|
+
this._approvalActions = options.approvalActions || FINANCIAL_APPROVAL_ACTIONS;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Validate content for financial claims and determine if human approval is needed.
|
|
305
|
+
* @param {string} content - Content to scan
|
|
306
|
+
* @returns {{ requiresHumanApproval: boolean, financialClaims: Array<{text: string, category: string, label: string}>, riskLevel: string }}
|
|
307
|
+
*/
|
|
308
|
+
validate(content) {
|
|
309
|
+
const financialClaims = [];
|
|
310
|
+
const contentLower = content.toLowerCase();
|
|
311
|
+
|
|
312
|
+
// Scan for financial patterns
|
|
313
|
+
for (const pattern of this._patterns) {
|
|
314
|
+
// Reset regex lastIndex for global patterns
|
|
315
|
+
pattern.regex.lastIndex = 0;
|
|
316
|
+
let match;
|
|
317
|
+
while ((match = pattern.regex.exec(content)) !== null) {
|
|
318
|
+
financialClaims.push({
|
|
319
|
+
text: match[0],
|
|
320
|
+
category: pattern.category,
|
|
321
|
+
label: pattern.label
|
|
322
|
+
});
|
|
323
|
+
// Avoid infinite loops on zero-length matches
|
|
324
|
+
if (match[0].length === 0) break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// Check if content mentions approval-required actions
|
|
329
|
+
const mentionsApprovalAction = this._approvalActions.some(action =>
|
|
330
|
+
contentLower.includes(action)
|
|
331
|
+
);
|
|
332
|
+
|
|
333
|
+
// Determine risk level
|
|
334
|
+
let riskLevel = 'low';
|
|
335
|
+
const hasTradeInstructions = financialClaims.some(c =>
|
|
336
|
+
c.category === 'trade_instruction' || c.category === 'transfer'
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
if (hasTradeInstructions) {
|
|
340
|
+
riskLevel = 'critical';
|
|
341
|
+
} else if (financialClaims.length > 3) {
|
|
342
|
+
riskLevel = 'high';
|
|
343
|
+
} else if (financialClaims.length > 0) {
|
|
344
|
+
riskLevel = 'medium';
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const requiresHumanApproval = mentionsApprovalAction || hasTradeInstructions;
|
|
348
|
+
|
|
349
|
+
if (requiresHumanApproval) {
|
|
350
|
+
console.log(`[Agent Shield] Fleet: Financial content requires human approval — ${financialClaims.length} claim(s), risk: ${riskLevel}`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return { requiresHumanApproval, financialClaims, riskLevel };
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// =========================================================================
|
|
358
|
+
// 4. DependencyDiversityScanner
|
|
359
|
+
// =========================================================================
|
|
360
|
+
|
|
361
|
+
/**
|
|
362
|
+
* Maps agent-to-server dependencies and identifies single points of
|
|
363
|
+
* failure where all agents depend on the same server/service.
|
|
364
|
+
*/
|
|
365
|
+
class DependencyDiversityScanner {
|
|
366
|
+
constructor() {
|
|
367
|
+
/** @type {Map<string, Set<string>>} agentId -> Set<serverId> */
|
|
368
|
+
this._agentDeps = new Map();
|
|
369
|
+
/** @type {Map<string, Set<string>>} serverId -> Set<agentId> */
|
|
370
|
+
this._serverDeps = new Map();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* Register a dependency: agent depends on server.
|
|
375
|
+
* @param {string} agentId - Agent identifier
|
|
376
|
+
* @param {string} serverId - Server/service identifier
|
|
377
|
+
*/
|
|
378
|
+
registerDependency(agentId, serverId) {
|
|
379
|
+
if (!this._agentDeps.has(agentId)) this._agentDeps.set(agentId, new Set());
|
|
380
|
+
this._agentDeps.get(agentId).add(serverId);
|
|
381
|
+
|
|
382
|
+
if (!this._serverDeps.has(serverId)) this._serverDeps.set(serverId, new Set());
|
|
383
|
+
this._serverDeps.get(serverId).add(agentId);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Analyze dependencies for single points of failure.
|
|
388
|
+
* @returns {{ singlePointsOfFailure: Array<{serverId: string, dependentAgents: string[]}>, diversityScore: number }}
|
|
389
|
+
*/
|
|
390
|
+
analyze() {
|
|
391
|
+
const totalAgents = this._agentDeps.size;
|
|
392
|
+
const singlePointsOfFailure = [];
|
|
393
|
+
|
|
394
|
+
// A SPOF is a server that ALL registered agents depend on
|
|
395
|
+
for (const [serverId, agents] of this._serverDeps.entries()) {
|
|
396
|
+
if (totalAgents > 0 && agents.size === totalAgents) {
|
|
397
|
+
singlePointsOfFailure.push({
|
|
398
|
+
serverId,
|
|
399
|
+
dependentAgents: [...agents]
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// Diversity score: 1.0 = no SPOFs, 0.0 = all servers are SPOFs
|
|
405
|
+
const totalServers = this._serverDeps.size;
|
|
406
|
+
const diversityScore = totalServers > 0
|
|
407
|
+
? 1 - (singlePointsOfFailure.length / totalServers)
|
|
408
|
+
: 1;
|
|
409
|
+
|
|
410
|
+
if (singlePointsOfFailure.length > 0) {
|
|
411
|
+
console.log(`[Agent Shield] Fleet: ${singlePointsOfFailure.length} single point(s) of failure detected`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
singlePointsOfFailure,
|
|
416
|
+
diversityScore: Math.round(diversityScore * 1000) / 1000
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Get dependencies for a specific agent.
|
|
422
|
+
* @param {string} agentId
|
|
423
|
+
* @returns {string[]}
|
|
424
|
+
*/
|
|
425
|
+
getAgentDependencies(agentId) {
|
|
426
|
+
const deps = this._agentDeps.get(agentId);
|
|
427
|
+
return deps ? [...deps] : [];
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/** Reset all state. */
|
|
431
|
+
reset() {
|
|
432
|
+
this._agentDeps.clear();
|
|
433
|
+
this._serverDeps.clear();
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// =========================================================================
|
|
438
|
+
// FleetDefense — Unified Wrapper
|
|
439
|
+
// =========================================================================
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Fleet Defense — wraps all four defense layers into a single class.
|
|
443
|
+
*/
|
|
444
|
+
class FleetDefense {
|
|
445
|
+
/**
|
|
446
|
+
* @param {object} [options]
|
|
447
|
+
* @param {object} [options.correlation] - Options for FleetCorrelationEngine
|
|
448
|
+
* @param {object} [options.financial] - Options for FinancialContentValidator
|
|
449
|
+
*/
|
|
450
|
+
constructor(options = {}) {
|
|
451
|
+
this.correlationEngine = new FleetCorrelationEngine(options.correlation);
|
|
452
|
+
this.cascadeBreaker = new CascadeBreaker();
|
|
453
|
+
this.financialValidator = new FinancialContentValidator(options.financial);
|
|
454
|
+
this.dependencyScanner = new DependencyDiversityScanner();
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Run a fleet health check.
|
|
459
|
+
* @returns {{ correlation: object, spof: object }}
|
|
460
|
+
*/
|
|
461
|
+
healthCheck() {
|
|
462
|
+
const correlation = this.correlationEngine.detectCorrelation();
|
|
463
|
+
const spof = this.dependencyScanner.analyze();
|
|
464
|
+
|
|
465
|
+
return { correlation, spof };
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// =========================================================================
|
|
470
|
+
// EXPORTS
|
|
471
|
+
// =========================================================================
|
|
472
|
+
|
|
473
|
+
module.exports = {
|
|
474
|
+
FleetDefense,
|
|
475
|
+
FleetCorrelationEngine,
|
|
476
|
+
CascadeBreaker,
|
|
477
|
+
FinancialContentValidator,
|
|
478
|
+
DependencyDiversityScanner,
|
|
479
|
+
FINANCIAL_PATTERNS,
|
|
480
|
+
FINANCIAL_APPROVAL_ACTIONS,
|
|
481
|
+
DEFAULT_CORRELATION_WINDOW_MS,
|
|
482
|
+
DEFAULT_CORRELATION_THRESHOLD
|
|
483
|
+
};
|