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,467 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Token-Level Analysis Module
5
+ *
6
+ * Detects prompt injection through statistical analysis of text.
7
+ * Uses Shannon entropy, character n-gram perplexity estimation,
8
+ * and vocabulary burst detection to identify injected content.
9
+ *
10
+ * All computation is pure JavaScript — no external dependencies.
11
+ * No data ever leaves the user's environment.
12
+ */
13
+
14
+ // =========================================================================
15
+ // TEXT STATISTICS (utility class)
16
+ // =========================================================================
17
+
18
+ /**
19
+ * Utility class for computing text statistics.
20
+ * All methods are static and operate on raw strings.
21
+ */
22
+ class TextStatistics {
23
+ /**
24
+ * Compute Shannon entropy of the character distribution in text.
25
+ * H = -sum(p * log2(p)) for each character frequency p.
26
+ * @param {string} text - Input text
27
+ * @returns {number} Shannon entropy in bits
28
+ */
29
+ static charEntropy(text) {
30
+ if (!text || text.length === 0) return 0;
31
+
32
+ const freq = {};
33
+ for (let i = 0; i < text.length; i++) {
34
+ const ch = text[i];
35
+ freq[ch] = (freq[ch] || 0) + 1;
36
+ }
37
+
38
+ const len = text.length;
39
+ let entropy = 0;
40
+ const keys = Object.keys(freq);
41
+ for (let i = 0; i < keys.length; i++) {
42
+ const p = freq[keys[i]] / len;
43
+ if (p > 0) {
44
+ entropy -= p * Math.log2(p);
45
+ }
46
+ }
47
+
48
+ return entropy;
49
+ }
50
+
51
+ /**
52
+ * Build a word frequency map from text.
53
+ * @param {string} text - Input text
54
+ * @returns {Object<string, number>} Map of word to count
55
+ */
56
+ static wordFrequency(text) {
57
+ if (!text || text.length === 0) return {};
58
+
59
+ const words = text.toLowerCase().match(/[a-z'-]+/g);
60
+ if (!words) return {};
61
+
62
+ const freq = {};
63
+ for (let i = 0; i < words.length; i++) {
64
+ const w = words[i];
65
+ freq[w] = (freq[w] || 0) + 1;
66
+ }
67
+ return freq;
68
+ }
69
+
70
+ /**
71
+ * Compute vocabulary richness as the type-token ratio.
72
+ * TTR = unique words / total words.
73
+ * @param {string} text - Input text
74
+ * @returns {number} Type-token ratio between 0 and 1
75
+ */
76
+ static vocabularyRichness(text) {
77
+ if (!text || text.length === 0) return 0;
78
+
79
+ const words = text.toLowerCase().match(/[a-z'-]+/g);
80
+ if (!words || words.length === 0) return 0;
81
+
82
+ const unique = new Set(words);
83
+ return unique.size / words.length;
84
+ }
85
+
86
+ /**
87
+ * Compute the average word length.
88
+ * @param {string} text - Input text
89
+ * @returns {number} Mean word length in characters
90
+ */
91
+ static averageWordLength(text) {
92
+ if (!text || text.length === 0) return 0;
93
+
94
+ const words = text.match(/[a-zA-Z'-]+/g);
95
+ if (!words || words.length === 0) return 0;
96
+
97
+ let total = 0;
98
+ for (let i = 0; i < words.length; i++) {
99
+ total += words[i].length;
100
+ }
101
+ return total / words.length;
102
+ }
103
+
104
+ /**
105
+ * Compute sentence complexity as average words per sentence.
106
+ * @param {string} text - Input text
107
+ * @returns {number} Average number of words per sentence
108
+ */
109
+ static sentenceComplexity(text) {
110
+ if (!text || text.length === 0) return 0;
111
+
112
+ const sentences = text.split(/[.!?]+/).filter(s => s.trim().length > 0);
113
+ if (sentences.length === 0) return 0;
114
+
115
+ let totalWords = 0;
116
+ for (let i = 0; i < sentences.length; i++) {
117
+ const words = sentences[i].trim().match(/\S+/g);
118
+ if (words) {
119
+ totalWords += words.length;
120
+ }
121
+ }
122
+ return totalWords / sentences.length;
123
+ }
124
+ }
125
+
126
+ // =========================================================================
127
+ // ENTROPY ANALYZER
128
+ // =========================================================================
129
+
130
+ /**
131
+ * Detects prompt injection via Shannon entropy shifts across text segments.
132
+ * Injected instructions often have markedly different entropy than natural
133
+ * conversational text, creating detectable anomalies.
134
+ */
135
+ class EntropyAnalyzer {
136
+ /**
137
+ * Create an EntropyAnalyzer.
138
+ * @param {Object} [options] - Configuration options
139
+ * @param {number} [options.threshold=0.3] - Entropy shift threshold for flagging anomalies
140
+ * @param {number} [options.windowSize=200] - Segment size in characters
141
+ */
142
+ constructor(options = {}) {
143
+ this.threshold = options.threshold !== undefined ? options.threshold : 0.3;
144
+ this.windowSize = options.windowSize !== undefined ? options.windowSize : 200;
145
+ }
146
+
147
+ /**
148
+ * Analyze text for entropy-based anomalies.
149
+ * Splits text into segments, computes per-segment entropy, and flags
150
+ * segments that deviate significantly from the overall average.
151
+ * @param {string} text - Input text to analyze
152
+ * @returns {{entropy: number, segments: Array<{text: string, entropy: number, suspicious: boolean}>, anomalies: Array<{text: string, entropy: number, deviation: number, position: number}>}}
153
+ */
154
+ analyze(text) {
155
+ if (!text || text.length === 0) {
156
+ return { entropy: 0, segments: [], anomalies: [] };
157
+ }
158
+
159
+ const overallEntropy = TextStatistics.charEntropy(text);
160
+
161
+ // Split text into segments of windowSize characters
162
+ const segments = [];
163
+ for (let i = 0; i < text.length; i += this.windowSize) {
164
+ const segText = text.slice(i, i + this.windowSize);
165
+ const segEntropy = TextStatistics.charEntropy(segText);
166
+ segments.push({
167
+ text: segText,
168
+ entropy: segEntropy,
169
+ suspicious: false
170
+ });
171
+ }
172
+
173
+ // Compute average segment entropy
174
+ let entropySum = 0;
175
+ for (let i = 0; i < segments.length; i++) {
176
+ entropySum += segments[i].entropy;
177
+ }
178
+ const avgEntropy = segments.length > 0 ? entropySum / segments.length : 0;
179
+
180
+ // Flag segments where entropy deviates beyond threshold
181
+ const anomalies = [];
182
+ for (let i = 0; i < segments.length; i++) {
183
+ const deviation = Math.abs(segments[i].entropy - avgEntropy);
184
+ if (deviation > this.threshold) {
185
+ segments[i].suspicious = true;
186
+ anomalies.push({
187
+ text: segments[i].text,
188
+ entropy: segments[i].entropy,
189
+ deviation: deviation,
190
+ position: i * this.windowSize
191
+ });
192
+ }
193
+ }
194
+
195
+ if (anomalies.length > 0) {
196
+ console.log('[Agent Shield] Entropy anomalies detected: ' + anomalies.length + ' segment(s) flagged');
197
+ }
198
+
199
+ return {
200
+ entropy: overallEntropy,
201
+ segments: segments,
202
+ anomalies: anomalies
203
+ };
204
+ }
205
+ }
206
+
207
+ // =========================================================================
208
+ // PERPLEXITY ESTIMATOR
209
+ // =========================================================================
210
+
211
+ /**
212
+ * Built-in English baseline corpus for bootstrapping the n-gram model.
213
+ * Common words and phrases that represent normal English text.
214
+ */
215
+ const ENGLISH_BASELINE = [
216
+ 'the quick brown fox jumps over the lazy dog',
217
+ 'to be or not to be that is the question',
218
+ 'I would like to help you with your request today',
219
+ 'please let me know if you have any questions about this',
220
+ 'thank you for your patience and understanding',
221
+ 'the weather is nice today and I hope you are doing well',
222
+ 'can you please provide more information about your issue',
223
+ 'I am happy to assist you with anything you need',
224
+ 'this is a common question and here is the answer',
225
+ 'we appreciate your feedback and will work to improve',
226
+ 'hello how are you doing today I hope everything is going well',
227
+ 'the project is progressing smoothly and we are on schedule',
228
+ 'please review the following document and provide your comments',
229
+ 'I understand your concern and will look into this matter',
230
+ 'the meeting has been scheduled for next week at the usual time'
231
+ ];
232
+
233
+ /**
234
+ * Estimates text perplexity using character n-gram frequency models.
235
+ * High perplexity relative to a trained baseline suggests the text
236
+ * deviates from normal patterns, indicating potential injection.
237
+ */
238
+ class PerplexityEstimator {
239
+ /**
240
+ * Create a PerplexityEstimator.
241
+ * @param {Object} [options] - Configuration options
242
+ * @param {number} [options.ngramSize=3] - Character n-gram size
243
+ */
244
+ constructor(options = {}) {
245
+ this.ngramSize = options.ngramSize !== undefined ? options.ngramSize : 3;
246
+ this.ngramCounts = {};
247
+ this.totalNgrams = 0;
248
+ this.trained = false;
249
+ this.baselinePerplexity = 0;
250
+
251
+ // Automatically train on the built-in English baseline
252
+ this.train(ENGLISH_BASELINE);
253
+ }
254
+
255
+ /**
256
+ * Build an n-gram frequency model from an array of normal texts.
257
+ * Subsequent calls to train() will add to the existing model.
258
+ * @param {string[]} corpusTexts - Array of normal/clean text samples
259
+ */
260
+ train(corpusTexts) {
261
+ if (!corpusTexts || corpusTexts.length === 0) return;
262
+
263
+ for (let t = 0; t < corpusTexts.length; t++) {
264
+ const text = corpusTexts[t].toLowerCase();
265
+ const maxI = Math.max(-1, text.length - this.ngramSize);
266
+ for (let i = 0; i <= maxI; i++) {
267
+ const ngram = text.slice(i, i + this.ngramSize);
268
+ this.ngramCounts[ngram] = (this.ngramCounts[ngram] || 0) + 1;
269
+ this.totalNgrams++;
270
+ }
271
+ }
272
+
273
+ this.trained = true;
274
+
275
+ // Compute baseline perplexity from the training corpus
276
+ let totalPerplexity = 0;
277
+ for (let t = 0; t < corpusTexts.length; t++) {
278
+ totalPerplexity += this._computePerplexity(corpusTexts[t]);
279
+ }
280
+ this.baselinePerplexity = totalPerplexity / corpusTexts.length;
281
+
282
+ console.log('[Agent Shield] Perplexity model trained: ' + this.totalNgrams + ' n-grams, baseline perplexity=' + this.baselinePerplexity.toFixed(2));
283
+ }
284
+
285
+ /**
286
+ * Compute raw perplexity for a given text using the trained model.
287
+ * @param {string} text - Input text
288
+ * @returns {number} Perplexity score
289
+ * @private
290
+ */
291
+ _computePerplexity(text) {
292
+ if (!text || text.length < this.ngramSize) return 0;
293
+
294
+ const lowered = text.toLowerCase();
295
+ const ngramCount = lowered.length - this.ngramSize + 1;
296
+ if (ngramCount <= 0) return 0;
297
+
298
+ let logProbSum = 0;
299
+ const vocabSize = Object.keys(this.ngramCounts).length;
300
+
301
+ for (let i = 0; i <= lowered.length - this.ngramSize; i++) {
302
+ const ngram = lowered.slice(i, i + this.ngramSize);
303
+ // Laplace smoothing: add 1 to avoid zero probabilities
304
+ const count = (this.ngramCounts[ngram] || 0) + 1;
305
+ const prob = count / (this.totalNgrams + vocabSize);
306
+ logProbSum += Math.log2(prob);
307
+ }
308
+
309
+ const avgLogProb = logProbSum / ngramCount;
310
+ // Perplexity = 2^(-avg log2 probability)
311
+ return Math.pow(2, -avgLogProb);
312
+ }
313
+
314
+ /**
315
+ * Estimate whether text is suspicious based on its perplexity.
316
+ * @param {string} text - Input text to evaluate
317
+ * @returns {{perplexity: number, suspicious: boolean}}
318
+ */
319
+ estimate(text) {
320
+ if (!this.trained) {
321
+ console.log('[Agent Shield] Perplexity estimator not trained, returning neutral result');
322
+ return { perplexity: 0, suspicious: false };
323
+ }
324
+
325
+ const perplexity = this._computePerplexity(text);
326
+
327
+ // Text is suspicious if its perplexity is significantly higher
328
+ // than the baseline (more than 2x the baseline)
329
+ const suspicious = perplexity > this.baselinePerplexity * 2;
330
+
331
+ if (suspicious) {
332
+ console.log('[Agent Shield] High perplexity detected: ' + perplexity.toFixed(2) + ' (baseline: ' + this.baselinePerplexity.toFixed(2) + ')');
333
+ }
334
+
335
+ return {
336
+ perplexity: perplexity,
337
+ suspicious: suspicious
338
+ };
339
+ }
340
+ }
341
+
342
+ // =========================================================================
343
+ // BURST DETECTOR
344
+ // =========================================================================
345
+
346
+ /**
347
+ * Detects sudden topic or style shifts in text using vocabulary overlap
348
+ * between sliding windows. Injection payloads often introduce a burst of
349
+ * new terminology that diverges sharply from surrounding content.
350
+ */
351
+ class BurstDetector {
352
+ /**
353
+ * Create a BurstDetector.
354
+ * @param {Object} [options] - Configuration options
355
+ * @param {number} [options.sensitivity=0.5] - Detection sensitivity (0 to 1). Higher = more sensitive.
356
+ */
357
+ constructor(options = {}) {
358
+ this.sensitivity = options.sensitivity !== undefined ? options.sensitivity : 0.5;
359
+ }
360
+
361
+ /**
362
+ * Extract words from text as a set for vocabulary comparison.
363
+ * @param {string} text - Input text
364
+ * @returns {Set<string>} Set of lowercase words
365
+ * @private
366
+ */
367
+ _wordSet(text) {
368
+ const words = text.toLowerCase().match(/[a-z'-]+/g);
369
+ return new Set(words || []);
370
+ }
371
+
372
+ /**
373
+ * Compute Jaccard similarity between two sets.
374
+ * J(A, B) = |A ∩ B| / |A ∪ B|
375
+ * @param {Set<string>} setA
376
+ * @param {Set<string>} setB
377
+ * @returns {number} Similarity between 0 and 1
378
+ * @private
379
+ */
380
+ _jaccardSimilarity(setA, setB) {
381
+ if (setA.size === 0 && setB.size === 0) return 1;
382
+
383
+ let intersection = 0;
384
+ for (const item of setA) {
385
+ if (setB.has(item)) intersection++;
386
+ }
387
+
388
+ const union = setA.size + setB.size - intersection;
389
+ return union === 0 ? 1 : intersection / union;
390
+ }
391
+
392
+ /**
393
+ * Analyze text for vocabulary bursts indicating style/topic shifts.
394
+ * Uses sliding windows with vocabulary overlap measurement.
395
+ * @param {string} text - Input text to analyze
396
+ * @returns {{bursts: Array<{position: number, before: string, after: string, score: number}>, suspicious: boolean}}
397
+ */
398
+ analyze(text) {
399
+ if (!text || text.length === 0) {
400
+ return { bursts: [], suspicious: false };
401
+ }
402
+
403
+ // Split into sentences for natural boundary detection
404
+ const sentences = text.split(/(?<=[.!?\n])\s+/).filter(s => s.trim().length > 0);
405
+
406
+ if (sentences.length < 2) {
407
+ return { bursts: [], suspicious: false };
408
+ }
409
+
410
+ // Use a sliding window of sentences to detect vocabulary shifts
411
+ const windowSize = Math.max(1, Math.floor(sentences.length / 4));
412
+ const bursts = [];
413
+ // Threshold: lower sensitivity means we need a bigger vocabulary gap
414
+ const threshold = 1 - this.sensitivity;
415
+
416
+ for (let i = windowSize; i < sentences.length; i++) {
417
+ const beforeText = sentences.slice(Math.max(0, i - windowSize), i).join(' ');
418
+ const afterText = sentences.slice(i, Math.min(sentences.length, i + windowSize)).join(' ');
419
+
420
+ const beforeWords = this._wordSet(beforeText);
421
+ const afterWords = this._wordSet(afterText);
422
+
423
+ // Skip if either window has too few words
424
+ if (beforeWords.size < 3 || afterWords.size < 3) continue;
425
+
426
+ const similarity = this._jaccardSimilarity(beforeWords, afterWords);
427
+ const burstScore = 1 - similarity;
428
+
429
+ if (burstScore > threshold) {
430
+ // Compute character position
431
+ let position = 0;
432
+ for (let j = 0; j < i; j++) {
433
+ position += sentences[j].length + 1;
434
+ }
435
+
436
+ bursts.push({
437
+ position: position,
438
+ before: beforeText.slice(-100),
439
+ after: afterText.slice(0, 100),
440
+ score: burstScore
441
+ });
442
+ }
443
+ }
444
+
445
+ const suspicious = bursts.length > 0;
446
+
447
+ if (suspicious) {
448
+ console.log('[Agent Shield] Vocabulary burst detected: ' + bursts.length + ' transition(s) flagged');
449
+ }
450
+
451
+ return {
452
+ bursts: bursts,
453
+ suspicious: suspicious
454
+ };
455
+ }
456
+ }
457
+
458
+ // =========================================================================
459
+ // EXPORTS
460
+ // =========================================================================
461
+
462
+ module.exports = {
463
+ EntropyAnalyzer,
464
+ PerplexityEstimator,
465
+ BurstDetector,
466
+ TextStatistics
467
+ };