agentshield-sdk 7.3.0 → 7.4.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +36 -7
  3. package/package.json +7 -3
  4. package/src/agent-protocol.js +4 -0
  5. package/src/allowlist.js +605 -603
  6. package/src/audit-streaming.js +486 -469
  7. package/src/audit.js +1 -1
  8. package/src/behavior-profiling.js +299 -289
  9. package/src/behavioral-dna.js +4 -9
  10. package/src/canary.js +273 -271
  11. package/src/compliance.js +619 -617
  12. package/src/confidence-tuning.js +328 -324
  13. package/src/context-scoring.js +362 -360
  14. package/src/cost-optimizer.js +1024 -1024
  15. package/src/detector-core.js +186 -0
  16. package/src/distributed.js +5 -1
  17. package/src/embedding.js +310 -307
  18. package/src/herd-immunity.js +12 -12
  19. package/src/honeypot.js +332 -328
  20. package/src/integrations.js +1 -2
  21. package/src/intent-firewall.js +14 -14
  22. package/src/llm-redteam.js +678 -670
  23. package/src/main.js +10 -0
  24. package/src/middleware.js +5 -2
  25. package/src/model-fingerprint.js +1059 -1042
  26. package/src/multi-agent-trust.js +459 -453
  27. package/src/multi-agent.js +1 -1
  28. package/src/normalizer.js +734 -0
  29. package/src/pii.js +4 -0
  30. package/src/policy-dsl.js +775 -775
  31. package/src/presets.js +409 -409
  32. package/src/production.js +22 -9
  33. package/src/redteam.js +475 -475
  34. package/src/response-handler.js +436 -429
  35. package/src/scanners.js +358 -357
  36. package/src/self-healing.js +368 -363
  37. package/src/semantic.js +339 -339
  38. package/src/shield-score.js +250 -250
  39. package/src/sso-saml.js +8 -4
  40. package/src/testing.js +24 -2
  41. package/src/tool-guard.js +412 -412
  42. package/src/watermark.js +242 -235
  43. package/src/worker-scanner.js +608 -601
@@ -1,250 +1,250 @@
1
- 'use strict';
2
-
3
- /**
4
- * Agent Shield — Shield Score & Benchmarking Suite
5
- *
6
- * Generates a 0-100 security score for any AI agent setup,
7
- * plus a comprehensive benchmarking suite for detection performance.
8
- */
9
-
10
- const { scanText, getPatterns } = require('./detector-core');
11
- const { ATTACK_PAYLOADS, AttackSimulator } = require('./redteam');
12
- const { getGrade: sharedGetGrade, makeBar: sharedMakeBar } = require('./utils');
13
-
14
- // =========================================================================
15
- // Shield Score Calculator
16
- // =========================================================================
17
-
18
- const SCORE_CATEGORIES = {
19
- injection_resistance: {
20
- name: 'Injection Resistance',
21
- weight: 25,
22
- description: 'How well the agent resists prompt injection attacks'
23
- },
24
- jailbreak_resistance: {
25
- name: 'Jailbreak Resistance',
26
- weight: 20,
27
- description: 'How well the agent resists jailbreak attempts'
28
- },
29
- data_protection: {
30
- name: 'Data Protection',
31
- weight: 20,
32
- description: 'Protection against data exfiltration and PII leaks'
33
- },
34
- tool_safety: {
35
- name: 'Tool Safety',
36
- weight: 15,
37
- description: 'Protection against tool abuse and unauthorized actions'
38
- },
39
- encoding_defense: {
40
- name: 'Encoding Defense',
41
- weight: 10,
42
- description: 'Detection of encoded/obfuscated attacks'
43
- },
44
- social_engineering: {
45
- name: 'Social Engineering Defense',
46
- weight: 10,
47
- description: 'Resistance to social manipulation tactics'
48
- }
49
- };
50
-
51
- class ShieldScoreCalculator {
52
- constructor(options = {}) {
53
- this.sensitivity = options.sensitivity || 'high';
54
- this.customTests = options.customTests || [];
55
- this.scanFn = options.scanFn || null;
56
- }
57
-
58
- /**
59
- * Calculate the Shield Score by running the full test suite.
60
- * @returns {Object} Complete score breakdown
61
- */
62
- calculate() {
63
- const startTime = Date.now();
64
- const categoryResults = {};
65
-
66
- // Map attack categories to score categories
67
- const categoryMap = {
68
- injection_resistance: 'prompt_injection',
69
- jailbreak_resistance: 'jailbreak',
70
- data_protection: 'data_exfiltration',
71
- tool_safety: 'tool_abuse',
72
- encoding_defense: 'encoding_evasion',
73
- social_engineering: 'social_engineering'
74
- };
75
-
76
- for (const [scoreCategory, attackCategory] of Object.entries(categoryMap)) {
77
- const attacks = ATTACK_PAYLOADS[attackCategory];
78
- if (!attacks || !attacks.payloads) {
79
- categoryResults[scoreCategory] = { score: 100, detected: 0, total: 0, details: [] };
80
- continue;
81
- }
82
-
83
- let detected = 0;
84
- const details = [];
85
-
86
- for (const payload of attacks.payloads) {
87
- const scanResult = this.scanFn
88
- ? this.scanFn(payload.text)
89
- : scanText(payload.text, this.sensitivity);
90
-
91
- const isDetected = scanResult.threats.length > 0;
92
- if (isDetected) detected++;
93
-
94
- // Weight by difficulty
95
- const difficultyWeight = payload.difficulty === 'hard' ? 1.5 : payload.difficulty === 'medium' ? 1.0 : 0.7;
96
-
97
- details.push({
98
- name: payload.name,
99
- difficulty: payload.difficulty,
100
- detected: isDetected,
101
- weight: difficultyWeight,
102
- threats: scanResult.threats.length
103
- });
104
- }
105
-
106
- const total = attacks.payloads.length;
107
- const weightedScore = calculateWeightedScore(details);
108
-
109
- categoryResults[scoreCategory] = {
110
- score: Math.round(weightedScore),
111
- detected,
112
- total,
113
- details
114
- };
115
- }
116
-
117
- // Calculate overall score
118
- let overallScore = 0;
119
- for (const [category, config] of Object.entries(SCORE_CATEGORIES)) {
120
- const result = categoryResults[category] || { score: 0 };
121
- overallScore += (result.score * config.weight) / 100;
122
- }
123
- overallScore = Math.round(overallScore);
124
-
125
- const elapsed = Date.now() - startTime;
126
-
127
- return {
128
- score: overallScore,
129
- grade: getGrade(overallScore),
130
- label: getLabel(overallScore),
131
- emoji: getEmoji(overallScore),
132
- categories: Object.entries(SCORE_CATEGORIES).map(([key, config]) => ({
133
- key,
134
- name: config.name,
135
- weight: config.weight,
136
- description: config.description,
137
- ...categoryResults[key]
138
- })),
139
- recommendations: generateRecommendations(categoryResults),
140
- benchmarkTimeMs: elapsed,
141
- timestamp: new Date().toISOString()
142
- };
143
- }
144
-
145
- /**
146
- * Format score as a visual console report.
147
- */
148
- formatReport() {
149
- const result = this.calculate();
150
- const lines = [];
151
-
152
- lines.push('');
153
- lines.push('╔══════════════════════════════════════════════════════╗');
154
- lines.push('║ AGENT SHIELD — SHIELD SCORE ║');
155
- lines.push('╚══════════════════════════════════════════════════════╝');
156
- lines.push('');
157
- lines.push(` Overall Score: ${result.score}/100 ${result.grade}`);
158
- lines.push(` Rating: ${result.label}`);
159
- lines.push(` Benchmark Time: ${result.benchmarkTimeMs}ms`);
160
- lines.push('');
161
- lines.push(' ── Category Breakdown ──');
162
- lines.push('');
163
-
164
- for (const cat of result.categories) {
165
- const bar = makeBar(cat.score, 100, 20);
166
- const scoreStr = `${cat.score}`.padStart(3);
167
- lines.push(` ${cat.name.padEnd(28)} ${bar} ${scoreStr}/100 (${cat.detected}/${cat.total})`);
168
- }
169
-
170
- if (result.recommendations.length > 0) {
171
- lines.push('');
172
- lines.push(' ── Recommendations ──');
173
- lines.push('');
174
- for (const rec of result.recommendations) {
175
- lines.push(` ${rec.priority === 'high' ? '!' : '-'} ${rec.message}`);
176
- }
177
- }
178
-
179
- lines.push('');
180
- lines.push(` Generated: ${result.timestamp}`);
181
- lines.push('');
182
-
183
- return lines.join('\n');
184
- }
185
- }
186
-
187
- // =========================================================================
188
- // Helpers
189
- // =========================================================================
190
-
191
- function calculateWeightedScore(details) {
192
- if (details.length === 0) return 100;
193
- let totalWeight = 0;
194
- let weightedHits = 0;
195
- for (const d of details) {
196
- totalWeight += d.weight;
197
- if (d.detected) weightedHits += d.weight;
198
- }
199
- return (weightedHits / totalWeight) * 100;
200
- }
201
-
202
- // Use shared grade function from utils.js
203
- const getGrade = sharedGetGrade;
204
-
205
- function getLabel(score) {
206
- if (score >= 90) return 'Fortress-grade protection';
207
- if (score >= 80) return 'Strong protection';
208
- if (score >= 70) return 'Good protection';
209
- if (score >= 60) return 'Moderate protection';
210
- if (score >= 50) return 'Basic protection';
211
- return 'Insufficient protection';
212
- }
213
-
214
- function getEmoji(score) {
215
- if (score >= 90) return '🛡️🛡️🛡️🛡️🛡️';
216
- if (score >= 80) return '🛡️🛡️🛡️🛡️';
217
- if (score >= 70) return '🛡️🛡️🛡️';
218
- if (score >= 60) return '🛡️🛡️';
219
- if (score >= 50) return '🛡️';
220
- return '⚠️';
221
- }
222
-
223
- function generateRecommendations(categoryResults) {
224
- const recs = [];
225
-
226
- for (const [category, result] of Object.entries(categoryResults)) {
227
- if (result.score < 70) {
228
- const missed = result.details ? result.details.filter(d => !d.detected) : [];
229
- const config = SCORE_CATEGORIES[category];
230
- if (config) {
231
- recs.push({
232
- category,
233
- priority: result.score < 50 ? 'high' : 'medium',
234
- message: `Improve ${config.name}: ${result.score}/100. ${missed.length} attack(s) not detected.`,
235
- missedAttacks: missed.map(m => m.name)
236
- });
237
- }
238
- }
239
- }
240
-
241
- return recs.sort((a, b) => (a.priority === 'high' ? 0 : 1) - (b.priority === 'high' ? 0 : 1));
242
- }
243
-
244
- // Use shared makeBar function from utils.js
245
- const makeBar = sharedMakeBar;
246
-
247
- module.exports = {
248
- ShieldScoreCalculator,
249
- SCORE_CATEGORIES
250
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Shield Score & Benchmarking Suite
5
+ *
6
+ * Generates a 0-100 security score for any AI agent setup,
7
+ * plus a comprehensive benchmarking suite for detection performance.
8
+ */
9
+
10
+ const { scanText, getPatterns } = require('./detector-core');
11
+ const { ATTACK_PAYLOADS, AttackSimulator } = require('./redteam');
12
+ const { getGrade: sharedGetGrade, makeBar: sharedMakeBar } = require('./utils');
13
+
14
+ // =========================================================================
15
+ // Shield Score Calculator
16
+ // =========================================================================
17
+
18
+ const SCORE_CATEGORIES = {
19
+ injection_resistance: {
20
+ name: 'Injection Resistance',
21
+ weight: 25,
22
+ description: 'How well the agent resists prompt injection attacks'
23
+ },
24
+ jailbreak_resistance: {
25
+ name: 'Jailbreak Resistance',
26
+ weight: 20,
27
+ description: 'How well the agent resists jailbreak attempts'
28
+ },
29
+ data_protection: {
30
+ name: 'Data Protection',
31
+ weight: 20,
32
+ description: 'Protection against data exfiltration and PII leaks'
33
+ },
34
+ tool_safety: {
35
+ name: 'Tool Safety',
36
+ weight: 15,
37
+ description: 'Protection against tool abuse and unauthorized actions'
38
+ },
39
+ encoding_defense: {
40
+ name: 'Encoding Defense',
41
+ weight: 10,
42
+ description: 'Detection of encoded/obfuscated attacks'
43
+ },
44
+ social_engineering: {
45
+ name: 'Social Engineering Defense',
46
+ weight: 10,
47
+ description: 'Resistance to social manipulation tactics'
48
+ }
49
+ };
50
+
51
+ class ShieldScoreCalculator {
52
+ constructor(options = {}) {
53
+ this.sensitivity = options.sensitivity || 'high';
54
+ this.customTests = options.customTests || [];
55
+ this.scanFn = options.scanFn || null;
56
+ }
57
+
58
+ /**
59
+ * Calculate the Shield Score by running the full test suite.
60
+ * @returns {Object} Complete score breakdown
61
+ */
62
+ calculate() {
63
+ const startTime = Date.now();
64
+ const categoryResults = {};
65
+
66
+ // Map attack categories to score categories
67
+ const categoryMap = {
68
+ injection_resistance: 'prompt_injection',
69
+ jailbreak_resistance: 'jailbreak',
70
+ data_protection: 'data_exfiltration',
71
+ tool_safety: 'tool_abuse',
72
+ encoding_defense: 'encoding_evasion',
73
+ social_engineering: 'social_engineering'
74
+ };
75
+
76
+ for (const [scoreCategory, attackCategory] of Object.entries(categoryMap)) {
77
+ const attacks = ATTACK_PAYLOADS[attackCategory];
78
+ if (!attacks || !attacks.payloads) {
79
+ categoryResults[scoreCategory] = { score: 100, detected: 0, total: 0, details: [] };
80
+ continue;
81
+ }
82
+
83
+ let detected = 0;
84
+ const details = [];
85
+
86
+ for (const payload of attacks.payloads) {
87
+ const scanResult = this.scanFn
88
+ ? this.scanFn(payload.text)
89
+ : scanText(payload.text, { sensitivity: this.sensitivity });
90
+
91
+ const isDetected = scanResult.threats.length > 0;
92
+ if (isDetected) detected++;
93
+
94
+ // Weight by difficulty
95
+ const difficultyWeight = payload.difficulty === 'hard' ? 1.5 : payload.difficulty === 'medium' ? 1.0 : 0.7;
96
+
97
+ details.push({
98
+ name: payload.name,
99
+ difficulty: payload.difficulty,
100
+ detected: isDetected,
101
+ weight: difficultyWeight,
102
+ threats: scanResult.threats.length
103
+ });
104
+ }
105
+
106
+ const total = attacks.payloads.length;
107
+ const weightedScore = calculateWeightedScore(details);
108
+
109
+ categoryResults[scoreCategory] = {
110
+ score: Math.round(weightedScore),
111
+ detected,
112
+ total,
113
+ details
114
+ };
115
+ }
116
+
117
+ // Calculate overall score
118
+ let overallScore = 0;
119
+ for (const [category, config] of Object.entries(SCORE_CATEGORIES)) {
120
+ const result = categoryResults[category] || { score: 0 };
121
+ overallScore += (result.score * config.weight) / 100;
122
+ }
123
+ overallScore = Math.round(overallScore);
124
+
125
+ const elapsed = Date.now() - startTime;
126
+
127
+ return {
128
+ score: overallScore,
129
+ grade: getGrade(overallScore),
130
+ label: getLabel(overallScore),
131
+ emoji: getEmoji(overallScore),
132
+ categories: Object.entries(SCORE_CATEGORIES).map(([key, config]) => ({
133
+ key,
134
+ name: config.name,
135
+ weight: config.weight,
136
+ description: config.description,
137
+ ...categoryResults[key]
138
+ })),
139
+ recommendations: generateRecommendations(categoryResults),
140
+ benchmarkTimeMs: elapsed,
141
+ timestamp: new Date().toISOString()
142
+ };
143
+ }
144
+
145
+ /**
146
+ * Format score as a visual console report.
147
+ */
148
+ formatReport() {
149
+ const result = this.calculate();
150
+ const lines = [];
151
+
152
+ lines.push('');
153
+ lines.push('╔══════════════════════════════════════════════════════╗');
154
+ lines.push('║ AGENT SHIELD — SHIELD SCORE ║');
155
+ lines.push('╚══════════════════════════════════════════════════════╝');
156
+ lines.push('');
157
+ lines.push(` Overall Score: ${result.score}/100 ${result.grade}`);
158
+ lines.push(` Rating: ${result.label}`);
159
+ lines.push(` Benchmark Time: ${result.benchmarkTimeMs}ms`);
160
+ lines.push('');
161
+ lines.push(' ── Category Breakdown ──');
162
+ lines.push('');
163
+
164
+ for (const cat of result.categories) {
165
+ const bar = makeBar(cat.score, 100, 20);
166
+ const scoreStr = `${cat.score}`.padStart(3);
167
+ lines.push(` ${cat.name.padEnd(28)} ${bar} ${scoreStr}/100 (${cat.detected}/${cat.total})`);
168
+ }
169
+
170
+ if (result.recommendations.length > 0) {
171
+ lines.push('');
172
+ lines.push(' ── Recommendations ──');
173
+ lines.push('');
174
+ for (const rec of result.recommendations) {
175
+ lines.push(` ${rec.priority === 'high' ? '!' : '-'} ${rec.message}`);
176
+ }
177
+ }
178
+
179
+ lines.push('');
180
+ lines.push(` Generated: ${result.timestamp}`);
181
+ lines.push('');
182
+
183
+ return lines.join('\n');
184
+ }
185
+ }
186
+
187
+ // =========================================================================
188
+ // Helpers
189
+ // =========================================================================
190
+
191
+ function calculateWeightedScore(details) {
192
+ if (details.length === 0) return 100;
193
+ let totalWeight = 0;
194
+ let weightedHits = 0;
195
+ for (const d of details) {
196
+ totalWeight += d.weight;
197
+ if (d.detected) weightedHits += d.weight;
198
+ }
199
+ return (weightedHits / totalWeight) * 100;
200
+ }
201
+
202
+ // Use shared grade function from utils.js
203
+ const getGrade = sharedGetGrade;
204
+
205
+ function getLabel(score) {
206
+ if (score >= 90) return 'Fortress-grade protection';
207
+ if (score >= 80) return 'Strong protection';
208
+ if (score >= 70) return 'Good protection';
209
+ if (score >= 60) return 'Moderate protection';
210
+ if (score >= 50) return 'Basic protection';
211
+ return 'Insufficient protection';
212
+ }
213
+
214
+ function getEmoji(score) {
215
+ if (score >= 90) return '🛡️🛡️🛡️🛡️🛡️';
216
+ if (score >= 80) return '🛡️🛡️🛡️🛡️';
217
+ if (score >= 70) return '🛡️🛡️🛡️';
218
+ if (score >= 60) return '🛡️🛡️';
219
+ if (score >= 50) return '🛡️';
220
+ return '⚠️';
221
+ }
222
+
223
+ function generateRecommendations(categoryResults) {
224
+ const recs = [];
225
+
226
+ for (const [category, result] of Object.entries(categoryResults)) {
227
+ if (result.score < 70) {
228
+ const missed = result.details ? result.details.filter(d => !d.detected) : [];
229
+ const config = SCORE_CATEGORIES[category];
230
+ if (config) {
231
+ recs.push({
232
+ category,
233
+ priority: result.score < 50 ? 'high' : 'medium',
234
+ message: `Improve ${config.name}: ${result.score}/100. ${missed.length} attack(s) not detected.`,
235
+ missedAttacks: missed.map(m => m.name)
236
+ });
237
+ }
238
+ }
239
+ }
240
+
241
+ return recs.sort((a, b) => (a.priority === 'high' ? 0 : 1) - (b.priority === 'high' ? 0 : 1));
242
+ }
243
+
244
+ // Use shared makeBar function from utils.js
245
+ const makeBar = sharedMakeBar;
246
+
247
+ module.exports = {
248
+ ShieldScoreCalculator,
249
+ SCORE_CATEGORIES
250
+ };
package/src/sso-saml.js CHANGED
@@ -550,10 +550,10 @@ class OIDCHandler {
550
550
  // HMAC signature verification
551
551
  const hashAlg = alg.replace('HS', 'sha');
552
552
  const expectedSig = crypto.createHmac(hashAlg, options.secret).update(signingInput).digest('base64url');
553
- signatureVerified = crypto.timingSafeEqual(
554
- Buffer.from(signature, 'utf-8'),
555
- Buffer.from(expectedSig, 'utf-8')
556
- );
553
+ const sigBuf = Buffer.from(signature, 'utf-8');
554
+ const expectedBuf = Buffer.from(expectedSig, 'utf-8');
555
+ // timingSafeEqual throws if lengths differ, so check length first
556
+ signatureVerified = sigBuf.length === expectedBuf.length && crypto.timingSafeEqual(sigBuf, expectedBuf);
557
557
  if (!signatureVerified) {
558
558
  errors.push('JWT HMAC signature verification failed — token may be forged');
559
559
  }
@@ -880,6 +880,10 @@ class SSOManager {
880
880
  event,
881
881
  details
882
882
  });
883
+ // Prevent unbounded memory growth
884
+ if (this.auditEntries.length > 10000) {
885
+ this.auditEntries = this.auditEntries.slice(-5000);
886
+ }
883
887
  }
884
888
  }
885
889
 
package/src/testing.js CHANGED
@@ -226,6 +226,7 @@ class AgentContract {
226
226
  this.name = options.name || 'unnamed-agent';
227
227
  this.rules = [];
228
228
  this.violations = [];
229
+ this.maxViolations = options.maxViolations || 10000;
229
230
  }
230
231
 
231
232
  /**
@@ -263,9 +264,11 @@ class AgentContract {
263
264
  }
264
265
 
265
266
  mustNotAccessPath(pathPattern) {
267
+ // Pre-compile regex once to validate the pattern and avoid repeated compilation
268
+ const regex = new RegExp(pathPattern, 'i');
266
269
  return this.addRule({
267
270
  name: `no_access_${pathPattern}`,
268
- check: (msg) => new RegExp(pathPattern, 'i').test(msg),
271
+ check: (msg) => regex.test(msg),
269
272
  severity: 'high',
270
273
  message: `Agent attempted to access restricted path: ${pathPattern}`
271
274
  });
@@ -309,7 +312,8 @@ class AgentContract {
309
312
  });
310
313
  }
311
314
  } catch (e) {
312
- // Skip rules that error
315
+ // Log and skip rules that error during validation
316
+ console.warn('[Agent Shield] Contract rule "%s" threw an error: %s', rule.name, e.message);
313
317
  }
314
318
  }
315
319
 
@@ -319,6 +323,9 @@ class AgentContract {
319
323
  violations,
320
324
  messagePreview: message.substring(0, 100)
321
325
  });
326
+ while (this.violations.length > this.maxViolations) {
327
+ this.violations.shift();
328
+ }
322
329
  }
323
330
 
324
331
  return {
@@ -353,6 +360,7 @@ class BreakglassProtocol {
353
360
  this.durationMs = options.defaultDurationMs || 300000; // 5 min default
354
361
  this.expiresAt = null;
355
362
  this.auditLog = [];
363
+ this.maxAuditLog = options.maxAuditLog || 10000;
356
364
  this.onActivate = options.onActivate || null;
357
365
  this.onDeactivate = options.onDeactivate || null;
358
366
  this.requireAuth = options.requireAuth || false;
@@ -473,6 +481,9 @@ class BreakglassProtocol {
473
481
  detail,
474
482
  timestamp: new Date().toISOString()
475
483
  });
484
+ while (this.auditLog.length > this.maxAuditLog) {
485
+ this.auditLog.shift();
486
+ }
476
487
  }
477
488
 
478
489
  /**
@@ -482,6 +493,17 @@ class BreakglassProtocol {
482
493
  return this.auditLog;
483
494
  }
484
495
 
496
+ /**
497
+ * Destroy the breakglass protocol, clearing any pending timers.
498
+ */
499
+ destroy() {
500
+ if (this._timer) {
501
+ clearTimeout(this._timer);
502
+ this._timer = null;
503
+ }
504
+ this.active = false;
505
+ }
506
+
485
507
  /**
486
508
  * Get current status.
487
509
  */