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.
Files changed (84) hide show
  1. package/CHANGELOG.md +191 -0
  2. package/LICENSE +21 -0
  3. package/README.md +975 -0
  4. package/bin/agent-shield.js +680 -0
  5. package/package.json +118 -0
  6. package/src/adaptive.js +330 -0
  7. package/src/agent-protocol.js +998 -0
  8. package/src/alert-tuning.js +480 -0
  9. package/src/allowlist.js +603 -0
  10. package/src/audit-immutable.js +914 -0
  11. package/src/audit-streaming.js +469 -0
  12. package/src/badges.js +196 -0
  13. package/src/behavior-profiling.js +289 -0
  14. package/src/benchmark-harness.js +804 -0
  15. package/src/canary.js +271 -0
  16. package/src/certification.js +563 -0
  17. package/src/circuit-breaker.js +321 -0
  18. package/src/compliance.js +617 -0
  19. package/src/confidence-tuning.js +324 -0
  20. package/src/confused-deputy.js +624 -0
  21. package/src/context-scoring.js +360 -0
  22. package/src/conversation.js +494 -0
  23. package/src/cost-optimizer.js +1024 -0
  24. package/src/ctf.js +462 -0
  25. package/src/detector-core.js +1999 -0
  26. package/src/distributed.js +359 -0
  27. package/src/document-scanner.js +795 -0
  28. package/src/embedding.js +307 -0
  29. package/src/encoding.js +429 -0
  30. package/src/enterprise.js +405 -0
  31. package/src/errors.js +100 -0
  32. package/src/eu-ai-act.js +523 -0
  33. package/src/fuzzer.js +764 -0
  34. package/src/honeypot.js +328 -0
  35. package/src/i18n-patterns.js +523 -0
  36. package/src/index.js +430 -0
  37. package/src/integrations.js +528 -0
  38. package/src/llm-redteam.js +670 -0
  39. package/src/main.js +741 -0
  40. package/src/main.mjs +38 -0
  41. package/src/mcp-bridge.js +542 -0
  42. package/src/mcp-certification.js +846 -0
  43. package/src/mcp-sdk-integration.js +355 -0
  44. package/src/mcp-security-runtime.js +741 -0
  45. package/src/mcp-server.js +740 -0
  46. package/src/middleware.js +208 -0
  47. package/src/model-finetuning.js +884 -0
  48. package/src/model-fingerprint.js +1042 -0
  49. package/src/multi-agent-trust.js +453 -0
  50. package/src/multi-agent.js +404 -0
  51. package/src/multimodal.js +296 -0
  52. package/src/nist-mapping.js +505 -0
  53. package/src/observability.js +330 -0
  54. package/src/openclaw.js +450 -0
  55. package/src/otel.js +544 -0
  56. package/src/owasp-2025.js +483 -0
  57. package/src/pii.js +390 -0
  58. package/src/plugin-marketplace.js +628 -0
  59. package/src/plugin-system.js +349 -0
  60. package/src/policy-dsl.js +775 -0
  61. package/src/policy-extended.js +635 -0
  62. package/src/policy.js +443 -0
  63. package/src/presets.js +409 -0
  64. package/src/production.js +557 -0
  65. package/src/prompt-leakage.js +321 -0
  66. package/src/rag-vulnerability.js +579 -0
  67. package/src/redteam.js +475 -0
  68. package/src/response-handler.js +429 -0
  69. package/src/scanners.js +357 -0
  70. package/src/self-healing.js +363 -0
  71. package/src/semantic.js +339 -0
  72. package/src/shield-score.js +250 -0
  73. package/src/sso-saml.js +897 -0
  74. package/src/stream-scanner.js +806 -0
  75. package/src/testing.js +505 -0
  76. package/src/threat-encyclopedia.js +629 -0
  77. package/src/threat-intel-network.js +1017 -0
  78. package/src/token-analysis.js +467 -0
  79. package/src/tool-guard.js +412 -0
  80. package/src/tool-output-validator.js +354 -0
  81. package/src/utils.js +83 -0
  82. package/src/watermark.js +235 -0
  83. package/src/worker-scanner.js +601 -0
  84. package/types/index.d.ts +2088 -0
@@ -0,0 +1,480 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Alert Fatigue Scoring & Auto-Tuning
5
+ *
6
+ * Features:
7
+ * - Alert fatigue analysis: identifies noisy patterns and high false-positive sources
8
+ * - Auto-tuning: applies suggestions to shield config to reduce alert noise
9
+ * - Alert correlation: groups related alerts by time proximity and category
10
+ */
11
+
12
+ // =========================================================================
13
+ // Alert Fatigue Analyzer
14
+ // =========================================================================
15
+
16
+ class AlertFatigueAnalyzer {
17
+ /**
18
+ * @param {Object} [options]
19
+ * @param {number} [options.windowSize=1000] - Maximum number of events in the analysis window.
20
+ * @param {number} [options.fatigueThreshold=0.7] - Score above which alerting is considered fatigued.
21
+ */
22
+ constructor(options = {}) {
23
+ this.windowSize = options.windowSize || 1000;
24
+ this.fatigueThreshold = options.fatigueThreshold || 0.7;
25
+ this.events = [];
26
+ }
27
+
28
+ /**
29
+ * Record an alert event for fatigue analysis.
30
+ * @param {Object} alertEvent - The alert event to record.
31
+ * @param {string} alertEvent.category - Alert category (e.g., 'prompt_injection').
32
+ * @param {string} alertEvent.severity - Severity level ('critical', 'high', 'medium', 'low').
33
+ * @param {string} alertEvent.pattern - The pattern or rule that fired.
34
+ * @param {boolean} [alertEvent.falsePositive=false] - Whether this was a false positive.
35
+ */
36
+ record(alertEvent) {
37
+ const event = {
38
+ category: alertEvent.category || 'unknown',
39
+ severity: alertEvent.severity || 'medium',
40
+ pattern: alertEvent.pattern || 'unknown',
41
+ falsePositive: alertEvent.falsePositive === true,
42
+ timestamp: Date.now()
43
+ };
44
+
45
+ this.events.push(event);
46
+
47
+ // Trim to window size
48
+ if (this.events.length > this.windowSize) {
49
+ this.events = this.events.slice(this.events.length - this.windowSize);
50
+ }
51
+
52
+ return event;
53
+ }
54
+
55
+ /**
56
+ * Return the patterns/categories with highest fire rate and lowest true-positive rate.
57
+ * @returns {Array<Object>} Sorted array of noisy sources [{pattern, category, fireCount, falsePositiveRate, truePositiveRate}].
58
+ */
59
+ getTopNoisy() {
60
+ if (this.events.length === 0) return [];
61
+
62
+ const patternStats = {};
63
+
64
+ for (const event of this.events) {
65
+ const key = `${event.category}::${event.pattern}`;
66
+ if (!patternStats[key]) {
67
+ patternStats[key] = {
68
+ pattern: event.pattern,
69
+ category: event.category,
70
+ fireCount: 0,
71
+ falsePositives: 0
72
+ };
73
+ }
74
+ patternStats[key].fireCount++;
75
+ if (event.falsePositive) {
76
+ patternStats[key].falsePositives++;
77
+ }
78
+ }
79
+
80
+ const results = Object.values(patternStats).map(stat => ({
81
+ pattern: stat.pattern,
82
+ category: stat.category,
83
+ fireCount: stat.fireCount,
84
+ falsePositiveRate: stat.fireCount > 0 ? stat.falsePositives / stat.fireCount : 0,
85
+ truePositiveRate: stat.fireCount > 0 ? (stat.fireCount - stat.falsePositives) / stat.fireCount : 1
86
+ }));
87
+
88
+ // Sort by highest fire rate and lowest true-positive rate (noisiest first)
89
+ results.sort((a, b) => {
90
+ const noiseA = a.fireCount * a.falsePositiveRate;
91
+ const noiseB = b.fireCount * b.falsePositiveRate;
92
+ return noiseB - noiseA;
93
+ });
94
+
95
+ return results;
96
+ }
97
+
98
+ /**
99
+ * Calculate a fatigue score from 0 to 1.
100
+ * High fire rate combined with many false positives produces a high fatigue score.
101
+ * @returns {number} Fatigue score between 0 (healthy) and 1 (severely fatigued).
102
+ */
103
+ getFatigueScore() {
104
+ if (this.events.length === 0) return 0;
105
+
106
+ const totalEvents = this.events.length;
107
+ const totalFalsePositives = this.events.filter(e => e.falsePositive).length;
108
+ const fpRate = totalFalsePositives / totalEvents;
109
+
110
+ // Fire rate: how full is the window relative to capacity
111
+ const fillRate = totalEvents / this.windowSize;
112
+
113
+ // Unique pattern count — fewer unique patterns with high volume = more fatigued
114
+ const uniquePatterns = new Set(this.events.map(e => `${e.category}::${e.pattern}`)).size;
115
+ const repetitionFactor = totalEvents > 0 ? 1 - (uniquePatterns / totalEvents) : 0;
116
+
117
+ // Weighted combination
118
+ const score = (fpRate * 0.5) + (fillRate * 0.25) + (repetitionFactor * 0.25);
119
+
120
+ return Math.min(1, Math.max(0, score));
121
+ }
122
+
123
+ /**
124
+ * Generate tuning suggestions to reduce alert fatigue.
125
+ * @returns {Array<Object>} Array of suggestions [{action, target, reason}].
126
+ */
127
+ suggest() {
128
+ const suggestions = [];
129
+ const noisy = this.getTopNoisy();
130
+ const fatigueScore = this.getFatigueScore();
131
+
132
+ if (fatigueScore < 0.3) {
133
+ return suggestions; // Alerting is healthy
134
+ }
135
+
136
+ for (const source of noisy) {
137
+ if (source.falsePositiveRate > 0.8) {
138
+ // Mostly false positives — suggest disabling
139
+ suggestions.push({
140
+ action: 'disable',
141
+ target: source.pattern,
142
+ reason: `Pattern "${source.pattern}" in category "${source.category}" has ${(source.falsePositiveRate * 100).toFixed(0)}% false positive rate (${source.fireCount} fires).`
143
+ });
144
+ } else if (source.falsePositiveRate > 0.5) {
145
+ // High FP rate — suggest adding allowlist
146
+ suggestions.push({
147
+ action: 'add_allowlist',
148
+ target: source.pattern,
149
+ reason: `Pattern "${source.pattern}" has ${(source.falsePositiveRate * 100).toFixed(0)}% false positive rate. Consider allowlisting common benign matches.`
150
+ });
151
+ } else if (source.fireCount > this.windowSize * 0.1 && source.falsePositiveRate > 0.2) {
152
+ // High volume with moderate FPs — refine the pattern
153
+ suggestions.push({
154
+ action: 'refine_pattern',
155
+ target: source.pattern,
156
+ reason: `Pattern "${source.pattern}" fires frequently (${source.fireCount} times) with ${(source.falsePositiveRate * 100).toFixed(0)}% false positives. Consider tightening the pattern.`
157
+ });
158
+ } else if (source.fireCount > this.windowSize * 0.15) {
159
+ // Very high volume but mostly true positives — lower severity to reduce noise
160
+ suggestions.push({
161
+ action: 'lower_severity',
162
+ target: source.pattern,
163
+ reason: `Pattern "${source.pattern}" fires very frequently (${source.fireCount} times). Consider lowering severity to reduce alert noise.`
164
+ });
165
+ }
166
+ }
167
+
168
+ return suggestions;
169
+ }
170
+
171
+ /**
172
+ * Reset all recorded events.
173
+ */
174
+ reset() {
175
+ this.events = [];
176
+ console.log('[Agent Shield] AlertFatigueAnalyzer reset.');
177
+ }
178
+ }
179
+
180
+ // =========================================================================
181
+ // Auto-Tuner
182
+ // =========================================================================
183
+
184
+ class AutoTuner {
185
+ /**
186
+ * @param {Object} shield - AgentShield instance to tune.
187
+ * @param {Object} [options]
188
+ * @param {boolean} [options.autoApply=false] - Whether to automatically apply suggestions.
189
+ */
190
+ constructor(shield, options = {}) {
191
+ this.shield = shield;
192
+ this.autoApply = options.autoApply || false;
193
+ this.analyzer = new AlertFatigueAnalyzer(options);
194
+ this.history = [];
195
+ this._lastApplied = null;
196
+ }
197
+
198
+ /**
199
+ * Run fatigue analysis on the shield's scan history.
200
+ * @returns {Object} Analysis result {fatigueScore, suggestions, topNoisy}.
201
+ */
202
+ analyze() {
203
+ // Pull scan history from the shield if available
204
+ const scanHistory = (this.shield && this.shield.stats && this.shield.stats.history) || [];
205
+
206
+ // Feed events into the analyzer
207
+ for (const entry of scanHistory) {
208
+ if (entry.threats && entry.threats.length > 0) {
209
+ for (const threat of entry.threats) {
210
+ this.analyzer.record({
211
+ category: threat.category || 'unknown',
212
+ severity: threat.severity || 'medium',
213
+ pattern: threat.description || threat.pattern || 'unknown',
214
+ falsePositive: threat.falsePositive === true
215
+ });
216
+ }
217
+ }
218
+ }
219
+
220
+ const fatigueScore = this.analyzer.getFatigueScore();
221
+ const suggestions = this.analyzer.suggest();
222
+ const topNoisy = this.analyzer.getTopNoisy();
223
+
224
+ const result = {
225
+ fatigueScore,
226
+ suggestions,
227
+ topNoisy,
228
+ timestamp: new Date().toISOString()
229
+ };
230
+
231
+ if (this.autoApply && suggestions.length > 0) {
232
+ this.apply(suggestions);
233
+ console.log(`[Agent Shield] AutoTuner auto-applied ${suggestions.length} suggestion(s).`);
234
+ }
235
+
236
+ return result;
237
+ }
238
+
239
+ /**
240
+ * Apply tuning suggestions to the shield config.
241
+ * @param {Array<Object>} suggestions - Array of suggestions from analyze().
242
+ * @returns {Object} Applied changes record.
243
+ */
244
+ apply(suggestions) {
245
+ const applied = {
246
+ timestamp: new Date().toISOString(),
247
+ changes: [],
248
+ previousConfig: this.shield.config ? JSON.parse(JSON.stringify(this.shield.config)) : {}
249
+ };
250
+
251
+ for (const suggestion of suggestions) {
252
+ const change = {
253
+ action: suggestion.action,
254
+ target: suggestion.target,
255
+ reason: suggestion.reason
256
+ };
257
+
258
+ switch (suggestion.action) {
259
+ case 'disable':
260
+ if (this.shield.config && this.shield.config.disabledPatterns) {
261
+ this.shield.config.disabledPatterns.push(suggestion.target);
262
+ }
263
+ change.applied = true;
264
+ break;
265
+
266
+ case 'lower_severity':
267
+ if (this.shield.config && this.shield.config.severityOverrides) {
268
+ this.shield.config.severityOverrides[suggestion.target] = 'low';
269
+ }
270
+ change.applied = true;
271
+ break;
272
+
273
+ case 'add_allowlist':
274
+ if (this.shield.config && this.shield.config.allowlist) {
275
+ this.shield.config.allowlist.push({ pattern: suggestion.target, reason: suggestion.reason });
276
+ }
277
+ change.applied = true;
278
+ break;
279
+
280
+ case 'refine_pattern':
281
+ // Cannot auto-apply pattern refinement — requires manual review
282
+ change.applied = false;
283
+ change.note = 'Pattern refinement requires manual review.';
284
+ break;
285
+
286
+ default:
287
+ change.applied = false;
288
+ break;
289
+ }
290
+
291
+ applied.changes.push(change);
292
+ }
293
+
294
+ this._lastApplied = applied;
295
+ this.history.push(applied);
296
+ console.log(`[Agent Shield] AutoTuner applied ${applied.changes.filter(c => c.applied).length} change(s).`);
297
+
298
+ return applied;
299
+ }
300
+
301
+ /**
302
+ * Undo the last applied tuning.
303
+ * @returns {boolean} Whether the revert was successful.
304
+ */
305
+ revert() {
306
+ if (!this._lastApplied) {
307
+ console.log('[Agent Shield] AutoTuner: nothing to revert.');
308
+ return false;
309
+ }
310
+
311
+ if (this.shield.config && this._lastApplied.previousConfig) {
312
+ Object.assign(this.shield.config, this._lastApplied.previousConfig);
313
+ }
314
+
315
+ const revertRecord = {
316
+ timestamp: new Date().toISOString(),
317
+ action: 'revert',
318
+ reverted: this._lastApplied.timestamp
319
+ };
320
+
321
+ this.history.push(revertRecord);
322
+ this._lastApplied = null;
323
+ console.log('[Agent Shield] AutoTuner reverted last tuning.');
324
+
325
+ return true;
326
+ }
327
+
328
+ /**
329
+ * Get the full tuning history.
330
+ * @returns {Array<Object>} Array of applied/reverted tuning records.
331
+ */
332
+ getHistory() {
333
+ return [...this.history];
334
+ }
335
+ }
336
+
337
+ // =========================================================================
338
+ // Alert Correlator
339
+ // =========================================================================
340
+
341
+ class AlertCorrelator {
342
+ /**
343
+ * @param {Object} [options]
344
+ * @param {number} [options.timeWindowMs=60000] - Time window for correlating alerts (default: 1 minute).
345
+ */
346
+ constructor(options = {}) {
347
+ this.timeWindowMs = options.timeWindowMs || 60000;
348
+ this._internalPatterns = [];
349
+ }
350
+
351
+ /**
352
+ * Group related alerts by time proximity and category.
353
+ * @param {Array<Object>} alerts - Array of alert objects with timestamp and category.
354
+ * @returns {Array<Object>} Array of correlated groups [{alerts, category, startTime, endTime, count}].
355
+ */
356
+ correlate(alerts) {
357
+ if (!alerts || alerts.length === 0) return [];
358
+
359
+ // Sort by timestamp
360
+ const sorted = [...alerts].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
361
+
362
+ const groups = [];
363
+ let currentGroup = null;
364
+
365
+ for (const alert of sorted) {
366
+ const ts = alert.timestamp || 0;
367
+ const cat = alert.category || 'unknown';
368
+
369
+ if (
370
+ currentGroup &&
371
+ currentGroup.category === cat &&
372
+ ts - currentGroup.endTime <= this.timeWindowMs
373
+ ) {
374
+ // Extend current group
375
+ currentGroup.alerts.push(alert);
376
+ currentGroup.endTime = ts;
377
+ currentGroup.count++;
378
+ } else {
379
+ // Start a new group
380
+ if (currentGroup) groups.push(currentGroup);
381
+ currentGroup = {
382
+ alerts: [alert],
383
+ category: cat,
384
+ startTime: ts,
385
+ endTime: ts,
386
+ count: 1
387
+ };
388
+ }
389
+ }
390
+
391
+ if (currentGroup) groups.push(currentGroup);
392
+
393
+ // Track patterns for getPatterns()
394
+ this._updatePatterns(groups);
395
+
396
+ return groups;
397
+ }
398
+
399
+ /**
400
+ * Remove duplicate alerts within the time window.
401
+ * Alerts are considered duplicates if they share the same category and pattern within the window.
402
+ * @param {Array<Object>} alerts - Array of alert objects.
403
+ * @returns {Array<Object>} Deduplicated alerts.
404
+ */
405
+ deduplicate(alerts) {
406
+ if (!alerts || alerts.length === 0) return [];
407
+
408
+ const sorted = [...alerts].sort((a, b) => (a.timestamp || 0) - (b.timestamp || 0));
409
+ const result = [];
410
+ const seen = new Map();
411
+
412
+ for (const alert of sorted) {
413
+ const key = `${alert.category || 'unknown'}::${alert.pattern || 'unknown'}`;
414
+ const ts = alert.timestamp || 0;
415
+
416
+ const lastSeen = seen.get(key);
417
+ if (lastSeen === undefined || ts - lastSeen > this.timeWindowMs) {
418
+ result.push(alert);
419
+ seen.set(key, ts);
420
+ }
421
+ }
422
+
423
+ return result;
424
+ }
425
+
426
+ /**
427
+ * Return recurring alert patterns observed from correlated groups.
428
+ * @returns {Array<Object>} Recurring patterns [{category, avgGroupSize, frequency, firstSeen, lastSeen}].
429
+ */
430
+ getPatterns() {
431
+ return [...this._internalPatterns];
432
+ }
433
+
434
+ /**
435
+ * Update internal pattern tracking from correlated groups.
436
+ * @private
437
+ * @param {Array<Object>} groups - Correlated alert groups.
438
+ */
439
+ _updatePatterns(groups) {
440
+ const patternMap = {};
441
+
442
+ for (const group of groups) {
443
+ const cat = group.category;
444
+ if (!patternMap[cat]) {
445
+ patternMap[cat] = {
446
+ category: cat,
447
+ groupSizes: [],
448
+ firstSeen: group.startTime,
449
+ lastSeen: group.endTime
450
+ };
451
+ }
452
+
453
+ patternMap[cat].groupSizes.push(group.count);
454
+ if (group.startTime < patternMap[cat].firstSeen) {
455
+ patternMap[cat].firstSeen = group.startTime;
456
+ }
457
+ if (group.endTime > patternMap[cat].lastSeen) {
458
+ patternMap[cat].lastSeen = group.endTime;
459
+ }
460
+ }
461
+
462
+ this._internalPatterns = Object.values(patternMap).map(p => ({
463
+ category: p.category,
464
+ avgGroupSize: p.groupSizes.length > 0 ? p.groupSizes.reduce((a, b) => a + b, 0) / p.groupSizes.length : 0,
465
+ frequency: p.groupSizes.length,
466
+ firstSeen: p.firstSeen,
467
+ lastSeen: p.lastSeen
468
+ }));
469
+ }
470
+ }
471
+
472
+ // =========================================================================
473
+ // Exports
474
+ // =========================================================================
475
+
476
+ module.exports = {
477
+ AlertFatigueAnalyzer,
478
+ AutoTuner,
479
+ AlertCorrelator
480
+ };