agentshield-sdk 7.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/CHANGELOG.md +191 -0
- package/LICENSE +21 -0
- package/README.md +975 -0
- package/bin/agent-shield.js +680 -0
- package/package.json +118 -0
- package/src/adaptive.js +330 -0
- package/src/agent-protocol.js +998 -0
- package/src/alert-tuning.js +480 -0
- package/src/allowlist.js +603 -0
- package/src/audit-immutable.js +914 -0
- package/src/audit-streaming.js +469 -0
- package/src/badges.js +196 -0
- package/src/behavior-profiling.js +289 -0
- package/src/benchmark-harness.js +804 -0
- package/src/canary.js +271 -0
- package/src/certification.js +563 -0
- package/src/circuit-breaker.js +321 -0
- package/src/compliance.js +617 -0
- package/src/confidence-tuning.js +324 -0
- package/src/confused-deputy.js +624 -0
- package/src/context-scoring.js +360 -0
- package/src/conversation.js +494 -0
- package/src/cost-optimizer.js +1024 -0
- package/src/ctf.js +462 -0
- package/src/detector-core.js +1999 -0
- package/src/distributed.js +359 -0
- package/src/document-scanner.js +795 -0
- package/src/embedding.js +307 -0
- package/src/encoding.js +429 -0
- package/src/enterprise.js +405 -0
- package/src/errors.js +100 -0
- package/src/eu-ai-act.js +523 -0
- package/src/fuzzer.js +764 -0
- package/src/honeypot.js +328 -0
- package/src/i18n-patterns.js +523 -0
- package/src/index.js +430 -0
- package/src/integrations.js +528 -0
- package/src/llm-redteam.js +670 -0
- package/src/main.js +741 -0
- package/src/main.mjs +38 -0
- package/src/mcp-bridge.js +542 -0
- package/src/mcp-certification.js +846 -0
- package/src/mcp-sdk-integration.js +355 -0
- package/src/mcp-security-runtime.js +741 -0
- package/src/mcp-server.js +740 -0
- package/src/middleware.js +208 -0
- package/src/model-finetuning.js +884 -0
- package/src/model-fingerprint.js +1042 -0
- package/src/multi-agent-trust.js +453 -0
- package/src/multi-agent.js +404 -0
- package/src/multimodal.js +296 -0
- package/src/nist-mapping.js +505 -0
- package/src/observability.js +330 -0
- package/src/openclaw.js +450 -0
- package/src/otel.js +544 -0
- package/src/owasp-2025.js +483 -0
- package/src/pii.js +390 -0
- package/src/plugin-marketplace.js +628 -0
- package/src/plugin-system.js +349 -0
- package/src/policy-dsl.js +775 -0
- package/src/policy-extended.js +635 -0
- package/src/policy.js +443 -0
- package/src/presets.js +409 -0
- package/src/production.js +557 -0
- package/src/prompt-leakage.js +321 -0
- package/src/rag-vulnerability.js +579 -0
- package/src/redteam.js +475 -0
- package/src/response-handler.js +429 -0
- package/src/scanners.js +357 -0
- package/src/self-healing.js +363 -0
- package/src/semantic.js +339 -0
- package/src/shield-score.js +250 -0
- package/src/sso-saml.js +897 -0
- package/src/stream-scanner.js +806 -0
- package/src/testing.js +505 -0
- package/src/threat-encyclopedia.js +629 -0
- package/src/threat-intel-network.js +1017 -0
- package/src/token-analysis.js +467 -0
- package/src/tool-guard.js +412 -0
- package/src/tool-output-validator.js +354 -0
- package/src/utils.js +83 -0
- package/src/watermark.js +235 -0
- package/src/worker-scanner.js +601 -0
- package/types/index.d.ts +2088 -0
|
@@ -0,0 +1,846 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Agent Shield — MCP Security Certification & Trust Framework
|
|
5
|
+
*
|
|
6
|
+
* Three components that create a competitive moat:
|
|
7
|
+
*
|
|
8
|
+
* 1. AgentThreatIntelligence — continuously updated attack pattern corpus
|
|
9
|
+
* that gets better with every deployment. A data moat.
|
|
10
|
+
*
|
|
11
|
+
* 2. MCPCertification — "Agent Shield Certified" attestation for MCP servers.
|
|
12
|
+
* If every MCP server runs certification, Agent Shield becomes the standard.
|
|
13
|
+
*
|
|
14
|
+
* 3. CrossOrgAgentTrust — certificate authority for AI agents communicating
|
|
15
|
+
* across organizational boundaries. The "TLS for AI agents."
|
|
16
|
+
*
|
|
17
|
+
* All processing runs locally — no data ever leaves your environment.
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
const crypto = require('crypto');
|
|
21
|
+
|
|
22
|
+
const LOG_PREFIX = '[Agent Shield]';
|
|
23
|
+
|
|
24
|
+
// =========================================================================
|
|
25
|
+
// Agent Threat Intelligence — the data moat
|
|
26
|
+
// =========================================================================
|
|
27
|
+
|
|
28
|
+
/** Built-in threat categories with severity weights. */
|
|
29
|
+
const THREAT_CATEGORIES = Object.freeze({
|
|
30
|
+
prompt_injection: { severity: 'critical', weight: 1.0 },
|
|
31
|
+
data_exfiltration: { severity: 'critical', weight: 1.0 },
|
|
32
|
+
privilege_escalation: { severity: 'critical', weight: 0.95 },
|
|
33
|
+
confused_deputy: { severity: 'high', weight: 0.9 },
|
|
34
|
+
tool_abuse: { severity: 'high', weight: 0.85 },
|
|
35
|
+
session_hijack: { severity: 'high', weight: 0.85 },
|
|
36
|
+
delegation_attack: { severity: 'high', weight: 0.8 },
|
|
37
|
+
prompt_leakage: { severity: 'medium', weight: 0.7 },
|
|
38
|
+
rag_poisoning: { severity: 'medium', weight: 0.7 },
|
|
39
|
+
behavioral_anomaly: { severity: 'medium', weight: 0.6 },
|
|
40
|
+
resource_abuse: { severity: 'low', weight: 0.4 },
|
|
41
|
+
policy_violation: { severity: 'low', weight: 0.3 }
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Local threat intelligence engine that learns from observed attacks.
|
|
46
|
+
* Maintains a pattern corpus that improves detection over time.
|
|
47
|
+
* All data stays local — nothing is ever transmitted.
|
|
48
|
+
*/
|
|
49
|
+
class AgentThreatIntelligence {
|
|
50
|
+
/**
|
|
51
|
+
* @param {object} [options]
|
|
52
|
+
* @param {number} [options.maxPatterns=10000] - Max patterns to store
|
|
53
|
+
* @param {number} [options.decayHalfLifeMs=604800000] - Pattern relevance decay (default 7 days)
|
|
54
|
+
* @param {number} [options.minConfidence=0.6] - Min confidence for pattern to be active
|
|
55
|
+
*/
|
|
56
|
+
constructor(options = {}) {
|
|
57
|
+
this._maxPatterns = options.maxPatterns || 10000;
|
|
58
|
+
this._decayHalfLife = options.decayHalfLifeMs || 604800000;
|
|
59
|
+
this._minConfidence = options.minConfidence || 0.6;
|
|
60
|
+
|
|
61
|
+
// Pattern corpus — the intelligence
|
|
62
|
+
this._patterns = new Map(); // patternId → PatternEntry
|
|
63
|
+
this._categoryStats = {};
|
|
64
|
+
for (const cat of Object.keys(THREAT_CATEGORIES)) {
|
|
65
|
+
this._categoryStats[cat] = { observed: 0, blocked: 0, bypassed: 0 };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Attack timeline for trend analysis
|
|
69
|
+
this._timeline = [];
|
|
70
|
+
this._maxTimeline = 10000;
|
|
71
|
+
|
|
72
|
+
this.stats = { patternsLearned: 0, attacksObserved: 0, trendsGenerated: 0 };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Records an observed attack for intelligence gathering.
|
|
77
|
+
* @param {object} attack
|
|
78
|
+
* @param {string} attack.category - Threat category
|
|
79
|
+
* @param {string} attack.pattern - Attack pattern/signature
|
|
80
|
+
* @param {string} [attack.source] - Where the attack was detected
|
|
81
|
+
* @param {object} [attack.context] - Additional context (tool, session, etc.)
|
|
82
|
+
* @param {boolean} [attack.blocked=true] - Whether the attack was blocked
|
|
83
|
+
* @returns {{ patternId: string, isNew: boolean, confidence: number }}
|
|
84
|
+
*/
|
|
85
|
+
recordAttack(attack) {
|
|
86
|
+
if (!attack.category || !attack.pattern) {
|
|
87
|
+
throw new Error(`${LOG_PREFIX} recordAttack requires category and pattern`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
this.stats.attacksObserved++;
|
|
91
|
+
const patternId = this._hashPattern(attack.category, attack.pattern);
|
|
92
|
+
|
|
93
|
+
// Update category stats
|
|
94
|
+
const catStats = this._categoryStats[attack.category];
|
|
95
|
+
if (catStats) {
|
|
96
|
+
catStats.observed++;
|
|
97
|
+
if (attack.blocked !== false) catStats.blocked++;
|
|
98
|
+
else catStats.bypassed++;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Record in timeline
|
|
102
|
+
if (this._timeline.length >= this._maxTimeline) {
|
|
103
|
+
this._timeline = this._timeline.slice(-Math.floor(this._maxTimeline * 0.75));
|
|
104
|
+
}
|
|
105
|
+
this._timeline.push({
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
category: attack.category,
|
|
108
|
+
patternId,
|
|
109
|
+
blocked: attack.blocked !== false,
|
|
110
|
+
source: attack.source || 'unknown'
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// Update or create pattern
|
|
114
|
+
const existing = this._patterns.get(patternId);
|
|
115
|
+
if (existing) {
|
|
116
|
+
existing.hitCount++;
|
|
117
|
+
existing.lastSeen = Date.now();
|
|
118
|
+
existing.confidence = Math.min(1.0, existing.confidence + 0.05);
|
|
119
|
+
if (attack.blocked === false) existing.bypassCount++;
|
|
120
|
+
return { patternId, isNew: false, confidence: existing.confidence };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// New pattern
|
|
124
|
+
const entry = {
|
|
125
|
+
patternId,
|
|
126
|
+
category: attack.category,
|
|
127
|
+
pattern: attack.pattern,
|
|
128
|
+
source: attack.source || 'unknown',
|
|
129
|
+
firstSeen: Date.now(),
|
|
130
|
+
lastSeen: Date.now(),
|
|
131
|
+
hitCount: 1,
|
|
132
|
+
bypassCount: attack.blocked === false ? 1 : 0,
|
|
133
|
+
confidence: 0.65,
|
|
134
|
+
context: attack.context || {}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
this._patterns.set(patternId, entry);
|
|
138
|
+
this.stats.patternsLearned++;
|
|
139
|
+
|
|
140
|
+
// Evict oldest patterns if at capacity
|
|
141
|
+
if (this._patterns.size > this._maxPatterns) {
|
|
142
|
+
this._evictOldest();
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return { patternId, isNew: true, confidence: entry.confidence };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Checks if input matches any known threat patterns.
|
|
150
|
+
* @param {string} input - Text to check
|
|
151
|
+
* @returns {{ matches: Array<{ patternId: string, category: string, confidence: number }>, riskScore: number }}
|
|
152
|
+
*/
|
|
153
|
+
checkAgainstIntel(input) {
|
|
154
|
+
const matches = [];
|
|
155
|
+
const lowerInput = input.toLowerCase();
|
|
156
|
+
|
|
157
|
+
for (const [_id, entry] of this._patterns) {
|
|
158
|
+
if (entry.confidence < this._minConfidence) continue;
|
|
159
|
+
const decayedConfidence = this._applyDecay(entry);
|
|
160
|
+
if (decayedConfidence < this._minConfidence) continue;
|
|
161
|
+
|
|
162
|
+
// Check if pattern appears in input
|
|
163
|
+
if (lowerInput.includes(entry.pattern.toLowerCase())) {
|
|
164
|
+
matches.push({
|
|
165
|
+
patternId: entry.patternId,
|
|
166
|
+
category: entry.category,
|
|
167
|
+
confidence: decayedConfidence,
|
|
168
|
+
hitCount: entry.hitCount,
|
|
169
|
+
firstSeen: entry.firstSeen
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Calculate composite risk score
|
|
175
|
+
let riskScore = 0;
|
|
176
|
+
for (const match of matches) {
|
|
177
|
+
const catWeight = (THREAT_CATEGORIES[match.category] || { weight: 0.5 }).weight;
|
|
178
|
+
riskScore = Math.max(riskScore, match.confidence * catWeight);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return { matches, riskScore: Math.round(riskScore * 100) / 100 };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Generates trend analysis from observed attacks.
|
|
186
|
+
* @param {number} [windowMs=86400000] - Analysis window (default 24 hours)
|
|
187
|
+
* @returns {{ topCategories: Array, attackRate: number, trendDirection: string, bypassRate: number }}
|
|
188
|
+
*/
|
|
189
|
+
getTrends(windowMs = 86400000) {
|
|
190
|
+
const cutoff = Date.now() - windowMs;
|
|
191
|
+
const recent = this._timeline.filter(e => e.timestamp > cutoff);
|
|
192
|
+
this.stats.trendsGenerated++;
|
|
193
|
+
|
|
194
|
+
// Category breakdown
|
|
195
|
+
const catCounts = {};
|
|
196
|
+
let blocked = 0;
|
|
197
|
+
let bypassed = 0;
|
|
198
|
+
for (const event of recent) {
|
|
199
|
+
catCounts[event.category] = (catCounts[event.category] || 0) + 1;
|
|
200
|
+
if (event.blocked) blocked++;
|
|
201
|
+
else bypassed++;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const topCategories = Object.entries(catCounts)
|
|
205
|
+
.sort((a, b) => b[1] - a[1])
|
|
206
|
+
.slice(0, 5)
|
|
207
|
+
.map(([category, count]) => ({ category, count, percentage: recent.length > 0 ? Math.round(count / recent.length * 100) : 0 }));
|
|
208
|
+
|
|
209
|
+
// Trend direction — compare first half vs second half
|
|
210
|
+
const midpoint = cutoff + windowMs / 2;
|
|
211
|
+
const firstHalf = recent.filter(e => e.timestamp < midpoint).length;
|
|
212
|
+
const secondHalf = recent.filter(e => e.timestamp >= midpoint).length;
|
|
213
|
+
let trendDirection = 'stable';
|
|
214
|
+
if (secondHalf > firstHalf * 1.5) trendDirection = 'increasing';
|
|
215
|
+
else if (secondHalf < firstHalf * 0.5) trendDirection = 'decreasing';
|
|
216
|
+
|
|
217
|
+
return {
|
|
218
|
+
topCategories,
|
|
219
|
+
attackRate: recent.length > 0 && windowMs > 0 ? Math.round(recent.length / Math.max(1, windowMs / 3600000) * 100) / 100 : 0,
|
|
220
|
+
trendDirection,
|
|
221
|
+
bypassRate: (blocked + bypassed) > 0 ? Math.round(bypassed / (blocked + bypassed) * 100) / 100 : 0,
|
|
222
|
+
totalObserved: recent.length,
|
|
223
|
+
window: { start: cutoff, end: Date.now() }
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Exports the intelligence corpus for backup/transfer.
|
|
229
|
+
* @returns {object} Serializable intelligence data
|
|
230
|
+
*/
|
|
231
|
+
exportCorpus() {
|
|
232
|
+
const patterns = [];
|
|
233
|
+
for (const [_id, entry] of this._patterns) {
|
|
234
|
+
patterns.push({ ...entry });
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
version: '1.0.0',
|
|
238
|
+
exportedAt: Date.now(),
|
|
239
|
+
stats: { ...this.stats },
|
|
240
|
+
categoryStats: { ...this._categoryStats },
|
|
241
|
+
patterns
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Imports intelligence corpus from another instance.
|
|
247
|
+
* @param {object} corpus - Output of exportCorpus()
|
|
248
|
+
* @returns {{ imported: number, merged: number, skipped: number }}
|
|
249
|
+
*/
|
|
250
|
+
importCorpus(corpus) {
|
|
251
|
+
if (!corpus || !corpus.patterns) {
|
|
252
|
+
throw new Error(`${LOG_PREFIX} Invalid corpus format`);
|
|
253
|
+
}
|
|
254
|
+
let imported = 0;
|
|
255
|
+
let merged = 0;
|
|
256
|
+
let skipped = 0;
|
|
257
|
+
|
|
258
|
+
for (const entry of corpus.patterns) {
|
|
259
|
+
// Validate entry structure
|
|
260
|
+
if (!entry.patternId || !entry.category || !entry.pattern ||
|
|
261
|
+
typeof entry.confidence !== 'number' || typeof entry.hitCount !== 'number') {
|
|
262
|
+
skipped++;
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const existing = this._patterns.get(entry.patternId);
|
|
267
|
+
if (existing) {
|
|
268
|
+
// Merge — take higher confidence and combined hit count
|
|
269
|
+
existing.hitCount += entry.hitCount;
|
|
270
|
+
existing.confidence = Math.max(existing.confidence, entry.confidence);
|
|
271
|
+
if (entry.lastSeen > existing.lastSeen) existing.lastSeen = entry.lastSeen;
|
|
272
|
+
merged++;
|
|
273
|
+
} else if (this._patterns.size < this._maxPatterns) {
|
|
274
|
+
this._patterns.set(entry.patternId, { ...entry });
|
|
275
|
+
imported++;
|
|
276
|
+
} else {
|
|
277
|
+
skipped++;
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return { imported, merged, skipped };
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/** @private */
|
|
285
|
+
_hashPattern(category, pattern) {
|
|
286
|
+
return crypto.createHash('sha256').update(`${category}:${pattern}`).digest('hex').substring(0, 16);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/** @private */
|
|
290
|
+
_applyDecay(entry) {
|
|
291
|
+
const age = Math.max(0, Date.now() - entry.lastSeen);
|
|
292
|
+
const halfLife = Math.max(1, this._decayHalfLife);
|
|
293
|
+
const decayFactor = Math.pow(0.5, age / halfLife);
|
|
294
|
+
return entry.confidence * decayFactor;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** @private */
|
|
298
|
+
_evictOldest() {
|
|
299
|
+
let oldest = null;
|
|
300
|
+
let oldestTime = Infinity;
|
|
301
|
+
for (const [id, entry] of this._patterns) {
|
|
302
|
+
if (entry.lastSeen < oldestTime) {
|
|
303
|
+
oldestTime = entry.lastSeen;
|
|
304
|
+
oldest = id;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (oldest) this._patterns.delete(oldest);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// =========================================================================
|
|
312
|
+
// MCP Certification — "Agent Shield Certified"
|
|
313
|
+
// =========================================================================
|
|
314
|
+
|
|
315
|
+
/** Certification requirements — what an MCP server must have. */
|
|
316
|
+
const CERTIFICATION_REQUIREMENTS = Object.freeze([
|
|
317
|
+
{
|
|
318
|
+
id: 'AUTH_001',
|
|
319
|
+
name: 'Per-User Authentication',
|
|
320
|
+
category: 'authentication',
|
|
321
|
+
severity: 'critical',
|
|
322
|
+
description: 'MCP server must authenticate individual users, not just agents',
|
|
323
|
+
check: (config) => config.enforceAuth !== false
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
id: 'AUTH_002',
|
|
327
|
+
name: 'Authorization Context Propagation',
|
|
328
|
+
category: 'authentication',
|
|
329
|
+
severity: 'critical',
|
|
330
|
+
description: 'Authorization context must flow through all tool calls',
|
|
331
|
+
check: (config) => config.contextPropagation !== false
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
id: 'AUTH_003',
|
|
335
|
+
name: 'Delegation Depth Limiting',
|
|
336
|
+
category: 'authentication',
|
|
337
|
+
severity: 'high',
|
|
338
|
+
description: 'Agent delegation chains must be depth-limited',
|
|
339
|
+
check: (config) => (config.maxDelegationDepth || 0) > 0 && (config.maxDelegationDepth || Infinity) <= 10
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
id: 'SCAN_001',
|
|
343
|
+
name: 'Tool Input Scanning',
|
|
344
|
+
category: 'scanning',
|
|
345
|
+
severity: 'critical',
|
|
346
|
+
description: 'All tool call arguments must be scanned for injection',
|
|
347
|
+
check: (config) => config.scanInputs !== false
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
id: 'SCAN_002',
|
|
351
|
+
name: 'Tool Output Scanning',
|
|
352
|
+
category: 'scanning',
|
|
353
|
+
severity: 'high',
|
|
354
|
+
description: 'Tool results must be scanned before returning to user',
|
|
355
|
+
check: (config) => config.scanOutputs !== false
|
|
356
|
+
},
|
|
357
|
+
{
|
|
358
|
+
id: 'SCAN_003',
|
|
359
|
+
name: 'Resource Content Scanning',
|
|
360
|
+
category: 'scanning',
|
|
361
|
+
severity: 'medium',
|
|
362
|
+
description: 'MCP resources should be scanned before exposure',
|
|
363
|
+
check: (config) => config.scanResources !== false
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: 'RATE_001',
|
|
367
|
+
name: 'Per-Session Rate Limiting',
|
|
368
|
+
category: 'rate_limiting',
|
|
369
|
+
severity: 'high',
|
|
370
|
+
description: 'Tool calls must be rate-limited per session',
|
|
371
|
+
check: (config) => (config.maxToolCallsPerSession || 0) > 0
|
|
372
|
+
},
|
|
373
|
+
{
|
|
374
|
+
id: 'RATE_002',
|
|
375
|
+
name: 'Token Budget Enforcement',
|
|
376
|
+
category: 'rate_limiting',
|
|
377
|
+
severity: 'medium',
|
|
378
|
+
description: 'Sessions must have token/cost budgets',
|
|
379
|
+
check: (config) => (config.maxTokenBudget || 0) > 0
|
|
380
|
+
},
|
|
381
|
+
{
|
|
382
|
+
id: 'AUDIT_001',
|
|
383
|
+
name: 'Audit Trail',
|
|
384
|
+
category: 'audit',
|
|
385
|
+
severity: 'critical',
|
|
386
|
+
description: 'All security events must be logged to an audit trail',
|
|
387
|
+
check: (config) => config.auditEnabled !== false
|
|
388
|
+
},
|
|
389
|
+
{
|
|
390
|
+
id: 'AUDIT_002',
|
|
391
|
+
name: 'Threat Event Callbacks',
|
|
392
|
+
category: 'audit',
|
|
393
|
+
severity: 'high',
|
|
394
|
+
description: 'Security team must be notified of threats in real-time',
|
|
395
|
+
check: (config) => typeof config.onThreat === 'function'
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: 'CRYPTO_001',
|
|
399
|
+
name: 'HMAC Context Signing',
|
|
400
|
+
category: 'cryptography',
|
|
401
|
+
severity: 'critical',
|
|
402
|
+
description: 'Authorization contexts must be HMAC-signed to prevent forgery',
|
|
403
|
+
check: (config) => config.signingKey && config.signingKey !== 'agent-shield-default-signing-key'
|
|
404
|
+
},
|
|
405
|
+
{
|
|
406
|
+
id: 'CRYPTO_002',
|
|
407
|
+
name: 'Ephemeral Token Credentials',
|
|
408
|
+
category: 'cryptography',
|
|
409
|
+
severity: 'high',
|
|
410
|
+
description: 'Tool access should use ephemeral tokens, not static credentials',
|
|
411
|
+
check: (config) => config.ephemeralTokens !== false
|
|
412
|
+
},
|
|
413
|
+
{
|
|
414
|
+
id: 'BEHAV_001',
|
|
415
|
+
name: 'Behavioral Monitoring',
|
|
416
|
+
category: 'monitoring',
|
|
417
|
+
severity: 'medium',
|
|
418
|
+
description: 'Agent behavior should be profiled for anomaly detection',
|
|
419
|
+
check: (config) => config.enableBehaviorMonitoring !== false
|
|
420
|
+
},
|
|
421
|
+
{
|
|
422
|
+
id: 'STATE_001',
|
|
423
|
+
name: 'Session State Machine',
|
|
424
|
+
category: 'session',
|
|
425
|
+
severity: 'high',
|
|
426
|
+
description: 'Sessions must enforce valid state transitions',
|
|
427
|
+
check: (config) => config.enableStateMachine !== false
|
|
428
|
+
},
|
|
429
|
+
{
|
|
430
|
+
id: 'POLICY_001',
|
|
431
|
+
name: 'Tool-Level Policies',
|
|
432
|
+
category: 'policy',
|
|
433
|
+
severity: 'high',
|
|
434
|
+
description: 'Individual tools must have declared security policies',
|
|
435
|
+
check: (config) => (config.registeredTools || 0) > 0
|
|
436
|
+
}
|
|
437
|
+
]);
|
|
438
|
+
|
|
439
|
+
/** Certification levels based on score. */
|
|
440
|
+
const CERTIFICATION_LEVELS = Object.freeze({
|
|
441
|
+
PLATINUM: { minScore: 95, label: 'Platinum', badge: '🛡️ Agent Shield Certified — Platinum' },
|
|
442
|
+
GOLD: { minScore: 80, label: 'Gold', badge: '🛡️ Agent Shield Certified — Gold' },
|
|
443
|
+
SILVER: { minScore: 65, label: 'Silver', badge: '🛡️ Agent Shield Certified — Silver' },
|
|
444
|
+
BRONZE: { minScore: 50, label: 'Bronze', badge: '🛡️ Agent Shield Certified — Bronze' },
|
|
445
|
+
NONE: { minScore: 0, label: 'Not Certified', badge: '⚠️ Not Certified' }
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Evaluates an MCP server configuration against Agent Shield certification requirements.
|
|
450
|
+
*/
|
|
451
|
+
class MCPCertification {
|
|
452
|
+
/**
|
|
453
|
+
* Runs certification audit against the provided configuration.
|
|
454
|
+
* @param {object} config - MCP server security configuration
|
|
455
|
+
* @returns {{ certified: boolean, level: string, score: number, badge: string, results: Array, recommendations: Array }}
|
|
456
|
+
*/
|
|
457
|
+
static evaluate(config = {}) {
|
|
458
|
+
const results = [];
|
|
459
|
+
let totalWeight = 0;
|
|
460
|
+
let earnedWeight = 0;
|
|
461
|
+
|
|
462
|
+
const severityWeights = { critical: 3, high: 2, medium: 1 };
|
|
463
|
+
|
|
464
|
+
for (const req of CERTIFICATION_REQUIREMENTS) {
|
|
465
|
+
const weight = severityWeights[req.severity] || 1;
|
|
466
|
+
totalWeight += weight;
|
|
467
|
+
|
|
468
|
+
let passed = false;
|
|
469
|
+
try {
|
|
470
|
+
passed = req.check(config);
|
|
471
|
+
} catch {
|
|
472
|
+
passed = false;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
if (passed) earnedWeight += weight;
|
|
476
|
+
|
|
477
|
+
results.push({
|
|
478
|
+
id: req.id,
|
|
479
|
+
name: req.name,
|
|
480
|
+
category: req.category,
|
|
481
|
+
severity: req.severity,
|
|
482
|
+
passed,
|
|
483
|
+
description: req.description
|
|
484
|
+
});
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
const score = Math.round(earnedWeight / totalWeight * 100);
|
|
488
|
+
|
|
489
|
+
// Determine level
|
|
490
|
+
let level = CERTIFICATION_LEVELS.NONE;
|
|
491
|
+
for (const l of [CERTIFICATION_LEVELS.PLATINUM, CERTIFICATION_LEVELS.GOLD, CERTIFICATION_LEVELS.SILVER, CERTIFICATION_LEVELS.BRONZE]) {
|
|
492
|
+
if (score >= l.minScore) { level = l; break; }
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// Generate recommendations for failed checks
|
|
496
|
+
const recommendations = results
|
|
497
|
+
.filter(r => !r.passed)
|
|
498
|
+
.sort((a, b) => (severityWeights[b.severity] || 0) - (severityWeights[a.severity] || 0))
|
|
499
|
+
.map(r => ({
|
|
500
|
+
id: r.id,
|
|
501
|
+
priority: r.severity,
|
|
502
|
+
action: r.description
|
|
503
|
+
}));
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
certified: score >= CERTIFICATION_LEVELS.BRONZE.minScore,
|
|
507
|
+
level: level.label,
|
|
508
|
+
score,
|
|
509
|
+
badge: level.badge,
|
|
510
|
+
timestamp: Date.now(),
|
|
511
|
+
results,
|
|
512
|
+
recommendations,
|
|
513
|
+
summary: {
|
|
514
|
+
total: results.length,
|
|
515
|
+
passed: results.filter(r => r.passed).length,
|
|
516
|
+
failed: results.filter(r => !r.passed).length,
|
|
517
|
+
criticalFailures: results.filter(r => !r.passed && r.severity === 'critical').length
|
|
518
|
+
}
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* Generates a certification report as formatted text.
|
|
524
|
+
* @param {object} evaluation - Output of evaluate()
|
|
525
|
+
* @returns {string}
|
|
526
|
+
*/
|
|
527
|
+
static formatReport(evaluation) {
|
|
528
|
+
const lines = [
|
|
529
|
+
'',
|
|
530
|
+
'═'.repeat(60),
|
|
531
|
+
' MCP Security Certification Report',
|
|
532
|
+
'═'.repeat(60),
|
|
533
|
+
'',
|
|
534
|
+
` Level: ${evaluation.badge}`,
|
|
535
|
+
` Score: ${evaluation.score}/100`,
|
|
536
|
+
` Date: ${new Date(evaluation.timestamp).toISOString().substring(0, 10)}`,
|
|
537
|
+
'',
|
|
538
|
+
` Results: ${evaluation.summary.passed}/${evaluation.summary.total} passed` +
|
|
539
|
+
(evaluation.summary.criticalFailures > 0 ? ` (${evaluation.summary.criticalFailures} critical failures)` : ''),
|
|
540
|
+
''
|
|
541
|
+
];
|
|
542
|
+
|
|
543
|
+
// Group by category
|
|
544
|
+
const categories = {};
|
|
545
|
+
for (const r of evaluation.results) {
|
|
546
|
+
if (!categories[r.category]) categories[r.category] = [];
|
|
547
|
+
categories[r.category].push(r);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
for (const [cat, items] of Object.entries(categories)) {
|
|
551
|
+
lines.push(` ${cat.toUpperCase()}`);
|
|
552
|
+
for (const item of items) {
|
|
553
|
+
const icon = item.passed ? '✓' : '✗';
|
|
554
|
+
lines.push(` ${icon} [${item.id}] ${item.name}`);
|
|
555
|
+
}
|
|
556
|
+
lines.push('');
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (evaluation.recommendations.length > 0) {
|
|
560
|
+
lines.push(' RECOMMENDATIONS');
|
|
561
|
+
for (const rec of evaluation.recommendations) {
|
|
562
|
+
lines.push(` [${rec.priority.toUpperCase()}] ${rec.action}`);
|
|
563
|
+
}
|
|
564
|
+
lines.push('');
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
lines.push('═'.repeat(60));
|
|
568
|
+
return lines.join('\n');
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// =========================================================================
|
|
573
|
+
// Cross-Organization Agent Trust — CA for AI agents
|
|
574
|
+
// =========================================================================
|
|
575
|
+
|
|
576
|
+
/**
|
|
577
|
+
* Certificate authority for AI agents crossing organizational boundaries.
|
|
578
|
+
* Issues, verifies, and revokes trust certificates for agents.
|
|
579
|
+
*
|
|
580
|
+
* When agents from different organizations need to interact via MCP,
|
|
581
|
+
* they present their trust certificate to prove identity and capabilities.
|
|
582
|
+
*/
|
|
583
|
+
class CrossOrgAgentTrust {
|
|
584
|
+
/**
|
|
585
|
+
* @param {object} options
|
|
586
|
+
* @param {string} options.orgId - Organization identifier
|
|
587
|
+
* @param {string} options.signingKey - HMAC key for certificate signing
|
|
588
|
+
* @param {number} [options.certificateTtlMs=86400000] - Certificate lifetime (default 24 hours)
|
|
589
|
+
* @param {number} [options.maxCertificates=1000] - Max active certificates
|
|
590
|
+
*/
|
|
591
|
+
constructor(options = {}) {
|
|
592
|
+
if (!options.orgId) throw new Error(`${LOG_PREFIX} CrossOrgAgentTrust requires orgId`);
|
|
593
|
+
if (!options.signingKey) throw new Error(`${LOG_PREFIX} CrossOrgAgentTrust requires signingKey`);
|
|
594
|
+
|
|
595
|
+
this._orgId = options.orgId;
|
|
596
|
+
this._signingKey = options.signingKey;
|
|
597
|
+
this._certificateTtlMs = options.certificateTtlMs || 86400000;
|
|
598
|
+
this._maxCertificates = options.maxCertificates || 1000;
|
|
599
|
+
|
|
600
|
+
// Active certificates
|
|
601
|
+
this._certificates = new Map(); // certId → certificate
|
|
602
|
+
this._revokedCerts = new Set();
|
|
603
|
+
this._trustedOrgs = new Map(); // orgId → { publicKey, trustLevel }
|
|
604
|
+
|
|
605
|
+
this.stats = { issued: 0, verified: 0, rejected: 0, revoked: 0 };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
/**
|
|
609
|
+
* Issues a trust certificate for an agent.
|
|
610
|
+
* @param {object} params
|
|
611
|
+
* @param {string} params.agentId - Agent identity
|
|
612
|
+
* @param {string[]} params.capabilities - What the agent can do
|
|
613
|
+
* @param {string[]} [params.allowedOrgs] - Which orgs this agent can interact with ('*' = any)
|
|
614
|
+
* @param {number} [params.trustLevel=5] - Trust level 1-10
|
|
615
|
+
* @returns {object} Signed certificate
|
|
616
|
+
*/
|
|
617
|
+
issueCertificate(params) {
|
|
618
|
+
if (!params.agentId) throw new Error(`${LOG_PREFIX} issueCertificate requires agentId`);
|
|
619
|
+
|
|
620
|
+
const certId = crypto.randomUUID();
|
|
621
|
+
const now = Date.now();
|
|
622
|
+
|
|
623
|
+
const certificate = {
|
|
624
|
+
certId,
|
|
625
|
+
version: '1.0',
|
|
626
|
+
issuer: this._orgId,
|
|
627
|
+
subject: {
|
|
628
|
+
agentId: params.agentId,
|
|
629
|
+
orgId: this._orgId
|
|
630
|
+
},
|
|
631
|
+
capabilities: Object.freeze([...(params.capabilities || [])]),
|
|
632
|
+
allowedOrgs: params.allowedOrgs || ['*'],
|
|
633
|
+
trustLevel: Math.min(10, Math.max(1, params.trustLevel || 5)),
|
|
634
|
+
issuedAt: now,
|
|
635
|
+
expiresAt: now + this._certificateTtlMs,
|
|
636
|
+
serialNumber: crypto.randomBytes(8).toString('hex')
|
|
637
|
+
};
|
|
638
|
+
|
|
639
|
+
// Sign the certificate
|
|
640
|
+
certificate.signature = this._signCertificate(certificate);
|
|
641
|
+
|
|
642
|
+
this._certificates.set(certId, certificate);
|
|
643
|
+
this.stats.issued++;
|
|
644
|
+
|
|
645
|
+
// Evict if at capacity
|
|
646
|
+
if (this._certificates.size > this._maxCertificates) {
|
|
647
|
+
this._evictExpired();
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
return { ...certificate };
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Verifies a certificate's authenticity and validity.
|
|
655
|
+
* @param {object} certificate
|
|
656
|
+
* @returns {{ valid: boolean, reason?: string, trustLevel: number }}
|
|
657
|
+
*/
|
|
658
|
+
verifyCertificate(certificate) {
|
|
659
|
+
this.stats.verified++;
|
|
660
|
+
|
|
661
|
+
// Check revocation
|
|
662
|
+
if (this._revokedCerts.has(certificate.certId)) {
|
|
663
|
+
this.stats.rejected++;
|
|
664
|
+
return { valid: false, reason: 'Certificate has been revoked', trustLevel: 0 };
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
// Check expiry
|
|
668
|
+
if (Date.now() > certificate.expiresAt) {
|
|
669
|
+
this.stats.rejected++;
|
|
670
|
+
return { valid: false, reason: 'Certificate has expired', trustLevel: 0 };
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
// Validate signature format
|
|
674
|
+
if (typeof certificate.signature !== 'string' || !/^[0-9a-f]{64}$/i.test(certificate.signature)) {
|
|
675
|
+
this.stats.rejected++;
|
|
676
|
+
return { valid: false, reason: 'Invalid signature format', trustLevel: 0 };
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
// Verify signature — if from our org, use our key
|
|
680
|
+
if (certificate.issuer === this._orgId) {
|
|
681
|
+
const expectedSig = this._signCertificate(certificate);
|
|
682
|
+
try {
|
|
683
|
+
const valid = crypto.timingSafeEqual(
|
|
684
|
+
Buffer.from(certificate.signature, 'hex'),
|
|
685
|
+
Buffer.from(expectedSig, 'hex')
|
|
686
|
+
);
|
|
687
|
+
if (!valid) {
|
|
688
|
+
this.stats.rejected++;
|
|
689
|
+
return { valid: false, reason: 'Invalid signature', trustLevel: 0 };
|
|
690
|
+
}
|
|
691
|
+
} catch {
|
|
692
|
+
this.stats.rejected++;
|
|
693
|
+
return { valid: false, reason: 'Signature verification failed', trustLevel: 0 };
|
|
694
|
+
}
|
|
695
|
+
} else {
|
|
696
|
+
// External certificate — check if we trust the issuer
|
|
697
|
+
const trustedOrg = this._trustedOrgs.get(certificate.issuer);
|
|
698
|
+
if (!trustedOrg) {
|
|
699
|
+
this.stats.rejected++;
|
|
700
|
+
return { valid: false, reason: `Unknown issuer: ${certificate.issuer}`, trustLevel: 0 };
|
|
701
|
+
}
|
|
702
|
+
// Verify with trusted org's key
|
|
703
|
+
const expectedSig = this._signCertificateWithKey(certificate, trustedOrg.publicKey);
|
|
704
|
+
try {
|
|
705
|
+
const valid = crypto.timingSafeEqual(
|
|
706
|
+
Buffer.from(certificate.signature, 'hex'),
|
|
707
|
+
Buffer.from(expectedSig, 'hex')
|
|
708
|
+
);
|
|
709
|
+
if (!valid) {
|
|
710
|
+
this.stats.rejected++;
|
|
711
|
+
return { valid: false, reason: 'External certificate signature invalid', trustLevel: 0 };
|
|
712
|
+
}
|
|
713
|
+
} catch {
|
|
714
|
+
this.stats.rejected++;
|
|
715
|
+
return { valid: false, reason: 'External signature verification failed', trustLevel: 0 };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Check if certificate allows interaction with our org
|
|
720
|
+
const allowedOrgs = certificate.allowedOrgs || [];
|
|
721
|
+
if (!allowedOrgs.includes('*') && !allowedOrgs.includes(this._orgId)) {
|
|
722
|
+
this.stats.rejected++;
|
|
723
|
+
return { valid: false, reason: `Certificate does not authorize interaction with ${this._orgId}`, trustLevel: 0 };
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
return { valid: true, trustLevel: certificate.trustLevel || 5 };
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
/**
|
|
730
|
+
* Revokes a certificate.
|
|
731
|
+
* @param {string} certId
|
|
732
|
+
* @returns {boolean} True if certificate was found and revoked
|
|
733
|
+
*/
|
|
734
|
+
revokeCertificate(certId) {
|
|
735
|
+
this._revokedCerts.add(certId);
|
|
736
|
+
const existed = this._certificates.delete(certId);
|
|
737
|
+
if (existed) this.stats.revoked++;
|
|
738
|
+
return existed;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Registers a trusted external organization.
|
|
743
|
+
* @param {string} orgId - Organization identifier
|
|
744
|
+
* @param {string} publicKey - Their signing key for certificate verification
|
|
745
|
+
* @param {number} [trustLevel=5] - How much we trust them (1-10)
|
|
746
|
+
*/
|
|
747
|
+
trustOrganization(orgId, publicKey, trustLevel = 5) {
|
|
748
|
+
if (!publicKey || typeof publicKey !== 'string') {
|
|
749
|
+
throw new Error(`${LOG_PREFIX} trustOrganization requires a non-empty publicKey`);
|
|
750
|
+
}
|
|
751
|
+
this._trustedOrgs.set(orgId, {
|
|
752
|
+
publicKey,
|
|
753
|
+
trustLevel: Math.min(10, Math.max(1, trustLevel)),
|
|
754
|
+
trustedAt: Date.now()
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Removes trust for an organization.
|
|
760
|
+
* @param {string} orgId
|
|
761
|
+
*/
|
|
762
|
+
untrustOrganization(orgId) {
|
|
763
|
+
this._trustedOrgs.delete(orgId);
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
/**
|
|
767
|
+
* Returns trust status summary.
|
|
768
|
+
* @returns {object}
|
|
769
|
+
*/
|
|
770
|
+
getTrustReport() {
|
|
771
|
+
const activeCerts = [];
|
|
772
|
+
for (const [_id, cert] of this._certificates) {
|
|
773
|
+
if (Date.now() <= cert.expiresAt) {
|
|
774
|
+
activeCerts.push({
|
|
775
|
+
certId: cert.certId,
|
|
776
|
+
agentId: cert.subject.agentId,
|
|
777
|
+
trustLevel: cert.trustLevel,
|
|
778
|
+
expiresIn: cert.expiresAt - Date.now()
|
|
779
|
+
});
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
const trustedOrgs = [];
|
|
784
|
+
for (const [orgId, info] of this._trustedOrgs) {
|
|
785
|
+
trustedOrgs.push({ orgId, trustLevel: info.trustLevel, trustedAt: info.trustedAt });
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
return {
|
|
789
|
+
orgId: this._orgId,
|
|
790
|
+
stats: { ...this.stats },
|
|
791
|
+
activeCertificates: activeCerts.length,
|
|
792
|
+
revokedCertificates: this._revokedCerts.size,
|
|
793
|
+
trustedOrganizations: trustedOrgs,
|
|
794
|
+
certificates: activeCerts
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** @private */
|
|
799
|
+
_signCertificate(cert) {
|
|
800
|
+
return this._signCertificateWithKey(cert, this._signingKey);
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/** @private */
|
|
804
|
+
_signCertificateWithKey(cert, key) {
|
|
805
|
+
const data = `${cert.certId}:${cert.issuer}:${cert.subject.agentId}:${cert.subject.orgId}:${cert.capabilities.join(',')}:${cert.issuedAt}:${cert.expiresAt}:${cert.serialNumber}`;
|
|
806
|
+
return crypto.createHmac('sha256', key).update(data).digest('hex');
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
/** @private */
|
|
810
|
+
_evictExpired() {
|
|
811
|
+
const now = Date.now();
|
|
812
|
+
const expiredIds = [];
|
|
813
|
+
for (const [certId, cert] of this._certificates) {
|
|
814
|
+
if (now > cert.expiresAt) expiredIds.push(certId);
|
|
815
|
+
}
|
|
816
|
+
for (const certId of expiredIds) {
|
|
817
|
+
this._certificates.delete(certId);
|
|
818
|
+
this._revokedCerts.delete(certId);
|
|
819
|
+
}
|
|
820
|
+
// LRU fallback: if still over capacity, evict oldest valid certificate
|
|
821
|
+
if (this._certificates.size > this._maxCertificates) {
|
|
822
|
+
let oldestId = null;
|
|
823
|
+
let oldestTime = Infinity;
|
|
824
|
+
for (const [certId, cert] of this._certificates) {
|
|
825
|
+
if (cert.issuedAt < oldestTime) {
|
|
826
|
+
oldestTime = cert.issuedAt;
|
|
827
|
+
oldestId = certId;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
if (oldestId) this._certificates.delete(oldestId);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// =========================================================================
|
|
836
|
+
// Exports
|
|
837
|
+
// =========================================================================
|
|
838
|
+
|
|
839
|
+
module.exports = {
|
|
840
|
+
AgentThreatIntelligence,
|
|
841
|
+
MCPCertification,
|
|
842
|
+
CrossOrgAgentTrust,
|
|
843
|
+
THREAT_CATEGORIES,
|
|
844
|
+
CERTIFICATION_REQUIREMENTS,
|
|
845
|
+
CERTIFICATION_LEVELS
|
|
846
|
+
};
|