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,429 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Advanced Encoding Detection: Steganographic Detection (#37),
5
+ * Encoding Bruteforce Detection (#14), Indirect Injection via
6
+ * Structured Data (#15)
7
+ */
8
+
9
+ const { scanText } = require('./detector-core');
10
+
11
+ // =========================================================================
12
+ // STEGANOGRAPHIC DETECTION
13
+ // =========================================================================
14
+
15
+ /**
16
+ * Patterns that indicate steganographic hiding techniques.
17
+ */
18
+ const STEGO_PATTERNS = {
19
+ // Unicode direction markers used to hide text
20
+ bidi_override: /[\u200E\u200F\u202A-\u202E\u2066-\u2069]/g,
21
+
22
+ // Invisible Unicode characters beyond zero-width (excludes common whitespace)
23
+ invisible_chars: /[\u00AD\u034F\u061C\u115F\u1160\u17B4\u17B5\u180E\u200B-\u200F\u2060-\u2064\u2066-\u206F\uFEFF\uFFF9-\uFFFB]/g,
24
+
25
+ // Whitespace variations used as binary encoding
26
+ whitespace_encoding: /[\u0009\u000A\u000B\u000C\u000D\u0020\u0085\u00A0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000]/g,
27
+
28
+ // Tag characters (Unicode Tags block U+E0001–U+E007F) — can encode hidden text
29
+ tag_characters: /(?:\uDB40[\uDC01-\uDC7F])/g,
30
+
31
+ // Variation selectors (can modify rendering without visible change)
32
+ variation_selectors: /[\uFE00-\uFE0F]|(?:\uDB40[\uDD00-\uDDEF])/g
33
+ };
34
+
35
+ class SteganographyDetector {
36
+ /**
37
+ * @param {object} [options]
38
+ * @param {Function} [options.onDetection] - Callback on steganographic content found.
39
+ */
40
+ constructor(options = {}) {
41
+ this.onDetection = options.onDetection || null;
42
+ }
43
+
44
+ /**
45
+ * Scans text for steganographic hiding techniques.
46
+ *
47
+ * @param {string} text
48
+ * @returns {object} { found: boolean, techniques: Array, cleaned: string }
49
+ */
50
+ scan(text) {
51
+ if (!text) return { found: false, techniques: [], cleaned: text };
52
+
53
+ const techniques = [];
54
+ let cleaned = text;
55
+
56
+ // Check for bidirectional override characters
57
+ const bidiMatches = text.match(STEGO_PATTERNS.bidi_override);
58
+ if (bidiMatches && bidiMatches.length > 0) {
59
+ techniques.push({
60
+ type: 'bidi_override',
61
+ count: bidiMatches.length,
62
+ severity: 'high',
63
+ description: `${bidiMatches.length} bidirectional override character(s) found. Text direction may be manipulated to hide content.`
64
+ });
65
+ cleaned = cleaned.replace(STEGO_PATTERNS.bidi_override, '');
66
+ }
67
+
68
+ // Check for invisible characters
69
+ const invisibleMatches = text.match(STEGO_PATTERNS.invisible_chars);
70
+ if (invisibleMatches && invisibleMatches.length > 3) {
71
+ techniques.push({
72
+ type: 'invisible_chars',
73
+ count: invisibleMatches.length,
74
+ severity: 'medium',
75
+ description: `${invisibleMatches.length} invisible Unicode characters found. May encode hidden messages.`
76
+ });
77
+ cleaned = cleaned.replace(STEGO_PATTERNS.invisible_chars, '');
78
+ }
79
+
80
+ // Check for suspicious whitespace patterns (potential binary encoding)
81
+ const words = text.split(/\S+/);
82
+ const spaceCounts = words.map(w => w.length).filter(l => l > 0);
83
+ if (spaceCounts.length > 10) {
84
+ const uniqueSpacings = new Set(spaceCounts);
85
+ // Binary encoding would show exactly 2 space widths
86
+ if (uniqueSpacings.size === 2 && spaceCounts.length > 20) {
87
+ techniques.push({
88
+ type: 'whitespace_binary',
89
+ severity: 'high',
90
+ description: 'Suspicious binary whitespace pattern detected. Spaces may encode hidden data.'
91
+ });
92
+ }
93
+ }
94
+
95
+ // Check for Unicode tag characters
96
+ const tagMatches = text.match(STEGO_PATTERNS.tag_characters);
97
+ if (tagMatches && tagMatches.length > 0) {
98
+ techniques.push({
99
+ type: 'tag_characters',
100
+ count: tagMatches.length,
101
+ severity: 'critical',
102
+ description: `${tagMatches.length} Unicode tag character(s) found. These can encode entire hidden messages.`
103
+ });
104
+ cleaned = cleaned.replace(STEGO_PATTERNS.tag_characters, '');
105
+ }
106
+
107
+ // After cleaning, re-scan for injections that were hidden
108
+ if (techniques.length > 0 && cleaned !== text) {
109
+ const cleanedResult = scanText(cleaned, { source: 'stego_cleaned', sensitivity: 'high' });
110
+ if (cleanedResult.threats.length > 0) {
111
+ techniques.push({
112
+ type: 'hidden_injection',
113
+ severity: 'critical',
114
+ description: 'After removing steganographic characters, injection patterns were revealed.',
115
+ hiddenThreats: cleanedResult.threats
116
+ });
117
+ }
118
+ }
119
+
120
+ if (techniques.length > 0 && this.onDetection) {
121
+ this.onDetection({ techniques, timestamp: Date.now() });
122
+ }
123
+
124
+ return { found: techniques.length > 0, techniques, cleaned };
125
+ }
126
+ }
127
+
128
+ // =========================================================================
129
+ // ENCODING BRUTEFORCE DETECTOR
130
+ // =========================================================================
131
+
132
+ class EncodingBruteforceDetector {
133
+ /**
134
+ * Detects rapid-fire attempts with different encodings.
135
+ *
136
+ * @param {object} [options]
137
+ * @param {number} [options.windowMs=60000] - Time window.
138
+ * @param {number} [options.threshold=5] - Number of encoded inputs to flag as bruteforce.
139
+ * @param {Function} [options.onDetection] - Callback on bruteforce detected.
140
+ */
141
+ constructor(options = {}) {
142
+ this.windowMs = options.windowMs || 60000;
143
+ this.threshold = options.threshold || 5;
144
+ this.onDetection = options.onDetection || null;
145
+ this.encodedInputs = [];
146
+ }
147
+
148
+ /**
149
+ * Checks an input for encoding and tracks frequency.
150
+ *
151
+ * @param {string} text
152
+ * @returns {object} { encoded: boolean, encodingType: string|null, bruteforce: boolean, count: number }
153
+ */
154
+ check(text) {
155
+ if (!text) return { encoded: false, encodingType: null, bruteforce: false, count: 0 };
156
+
157
+ const encoding = this._detectEncoding(text);
158
+
159
+ if (encoding) {
160
+ const now = Date.now();
161
+ this.encodedInputs.push({ type: encoding, timestamp: now });
162
+
163
+ // Prune old entries
164
+ const cutoff = now - this.windowMs;
165
+ this.encodedInputs = this.encodedInputs.filter(e => e.timestamp > cutoff);
166
+
167
+ const isBruteforce = this.encodedInputs.length >= this.threshold;
168
+
169
+ if (isBruteforce && this.onDetection) {
170
+ const typeCounts = {};
171
+ for (const e of this.encodedInputs) {
172
+ typeCounts[e.type] = (typeCounts[e.type] || 0) + 1;
173
+ }
174
+ this.onDetection({
175
+ count: this.encodedInputs.length,
176
+ types: typeCounts,
177
+ timestamp: now
178
+ });
179
+ }
180
+
181
+ return {
182
+ encoded: true,
183
+ encodingType: encoding,
184
+ bruteforce: isBruteforce,
185
+ count: this.encodedInputs.length
186
+ };
187
+ }
188
+
189
+ return { encoded: false, encodingType: null, bruteforce: false, count: this.encodedInputs.length };
190
+ }
191
+
192
+ /** @private */
193
+ _detectEncoding(text) {
194
+ // Base64
195
+ if (/^[A-Za-z0-9+/]{20,}={0,2}$/.test(text.trim())) return 'base64';
196
+
197
+ // Hex encoding
198
+ if (/^(?:0x)?[0-9a-fA-F]{20,}$/i.test(text.trim())) return 'hex';
199
+ if (/(?:\\x[0-9a-fA-F]{2}){5,}/.test(text)) return 'hex_escape';
200
+
201
+ // URL encoding (high density)
202
+ const pctCount = (text.match(/%[0-9a-fA-F]{2}/g) || []).length;
203
+ if (pctCount > 5 && pctCount / text.length > 0.1) return 'url_encoding';
204
+
205
+ // HTML entities (high density)
206
+ const entityCount = (text.match(/&#\w+;/g) || []).length;
207
+ if (entityCount > 5) return 'html_entities';
208
+
209
+ // Unicode escapes
210
+ if (/(?:\\u[0-9a-fA-F]{4}){3,}/.test(text)) return 'unicode_escape';
211
+
212
+ // ROT13 heuristic (text looks like garbled English)
213
+ if (this._looksLikeRot13(text)) return 'rot13';
214
+
215
+ // Morse code
216
+ if (/^[\s./-]{20,}$/.test(text.trim()) && /\.{1,3}/.test(text) && /-{1,3}/.test(text)) return 'morse';
217
+
218
+ // Binary
219
+ if (/^[01\s]{20,}$/.test(text.trim()) && text.replace(/\s/g, '').length % 8 === 0) return 'binary';
220
+
221
+ return null;
222
+ }
223
+
224
+ /** @private */
225
+ _looksLikeRot13(text) {
226
+ if (text.length < 20 || !/^[a-zA-Z\s.,!?]+$/.test(text)) return false;
227
+
228
+ // Decode ROT13 and check if result has common English words
229
+ const decoded = text.replace(/[a-zA-Z]/g, c => {
230
+ const base = c <= 'Z' ? 65 : 97;
231
+ return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
232
+ });
233
+
234
+ const commonWords = /\b(?:the|and|for|are|but|not|you|all|can|had|her|was|one|our|ignore|previous|instructions|system|forget|override)\b/i;
235
+ return commonWords.test(decoded) && !commonWords.test(text);
236
+ }
237
+
238
+ reset() {
239
+ this.encodedInputs = [];
240
+ }
241
+ }
242
+
243
+ // =========================================================================
244
+ // STRUCTURED DATA INJECTION SCANNER
245
+ // =========================================================================
246
+
247
+ class StructuredDataScanner {
248
+ /**
249
+ * Scans JSON, XML, YAML, CSV, and other structured data for injections.
250
+ *
251
+ * @param {object} [options]
252
+ * @param {Function} [options.onDetection] - Callback on injection found.
253
+ */
254
+ constructor(options = {}) {
255
+ this.onDetection = options.onDetection || null;
256
+ }
257
+
258
+ /**
259
+ * Scans a JSON object for injection patterns in string values.
260
+ *
261
+ * @param {object|string} data - JSON object or JSON string.
262
+ * @param {string} [source='json_data'] - Source label.
263
+ * @returns {object} { clean: boolean, threats: Array }
264
+ */
265
+ scanJSON(data, source = 'json_data') {
266
+ let obj = data;
267
+ if (typeof data === 'string') {
268
+ try { obj = JSON.parse(data); } catch (e) { return { clean: true, threats: [] }; }
269
+ }
270
+
271
+ const strings = this._extractStrings(obj);
272
+ return this._scanStrings(strings, source);
273
+ }
274
+
275
+ /**
276
+ * Scans XML/HTML-like text for injections in attributes and content.
277
+ *
278
+ * @param {string} xml
279
+ * @param {string} [source='xml_data']
280
+ * @returns {object} { clean: boolean, threats: Array }
281
+ */
282
+ scanXML(xml, source = 'xml_data') {
283
+ if (!xml) return { clean: true, threats: [] };
284
+
285
+ const strings = [];
286
+
287
+ // Extract attribute values
288
+ const attrRegex = /\w+\s*=\s*["']([^"']+)["']/g;
289
+ let match;
290
+ while ((match = attrRegex.exec(xml)) !== null) {
291
+ strings.push({ value: match[1], path: `attribute:${match[0].split('=')[0].trim()}` });
292
+ }
293
+
294
+ // Extract text content between tags
295
+ const contentRegex = />([^<]+)</g;
296
+ while ((match = contentRegex.exec(xml)) !== null) {
297
+ const text = match[1].trim();
298
+ if (text.length > 5) {
299
+ strings.push({ value: text, path: 'text_content' });
300
+ }
301
+ }
302
+
303
+ // Extract CDATA sections
304
+ const cdataRegex = /<!\[CDATA\[([\s\S]*?)\]\]>/g;
305
+ while ((match = cdataRegex.exec(xml)) !== null) {
306
+ strings.push({ value: match[1], path: 'cdata' });
307
+ }
308
+
309
+ return this._scanStrings(strings, source);
310
+ }
311
+
312
+ /**
313
+ * Scans CSV data for injections.
314
+ *
315
+ * @param {string} csv
316
+ * @param {string} [source='csv_data']
317
+ * @returns {object} { clean: boolean, threats: Array }
318
+ */
319
+ scanCSV(csv, source = 'csv_data') {
320
+ if (!csv) return { clean: true, threats: [] };
321
+
322
+ const strings = [];
323
+ const lines = csv.split('\n');
324
+
325
+ for (let i = 0; i < lines.length; i++) {
326
+ const cells = lines[i].split(',').map(c => c.trim().replace(/^["']|["']$/g, ''));
327
+ for (let j = 0; j < cells.length; j++) {
328
+ if (cells[j].length > 5) {
329
+ strings.push({ value: cells[j], path: `row${i + 1}:col${j + 1}` });
330
+ }
331
+ }
332
+ }
333
+
334
+ return this._scanStrings(strings, source);
335
+ }
336
+
337
+ /**
338
+ * Scans Markdown for injections in various elements.
339
+ *
340
+ * @param {string} markdown
341
+ * @param {string} [source='markdown']
342
+ * @returns {object} { clean: boolean, threats: Array }
343
+ */
344
+ scanMarkdown(markdown, source = 'markdown') {
345
+ if (!markdown) return { clean: true, threats: [] };
346
+
347
+ // Scan the full markdown text
348
+ const result = scanText(markdown, { source, sensitivity: 'high' });
349
+
350
+ // Additionally check for suspicious elements
351
+ const strings = [];
352
+
353
+ // Link text and URLs
354
+ const linkRegex = /\[([^\]]*)\]\(([^)]*)\)/g;
355
+ let match;
356
+ while ((match = linkRegex.exec(markdown)) !== null) {
357
+ strings.push({ value: match[1], path: 'link_text' });
358
+ strings.push({ value: match[2], path: 'link_url' });
359
+ }
360
+
361
+ // Image alt text
362
+ const imgRegex = /!\[([^\]]*)\]\(([^)]*)\)/g;
363
+ while ((match = imgRegex.exec(markdown)) !== null) {
364
+ strings.push({ value: match[1], path: 'image_alt' });
365
+ }
366
+
367
+ // HTML comments in markdown
368
+ const commentRegex = /<!--([\s\S]*?)-->/g;
369
+ while ((match = commentRegex.exec(markdown)) !== null) {
370
+ strings.push({ value: match[1], path: 'html_comment' });
371
+ }
372
+
373
+ const structuredResult = this._scanStrings(strings, source);
374
+
375
+ return {
376
+ clean: result.threats.length === 0 && structuredResult.clean,
377
+ threats: [...result.threats, ...structuredResult.threats]
378
+ };
379
+ }
380
+
381
+ /** @private */
382
+ _extractStrings(obj, path = '', depth = 0) {
383
+ const strings = [];
384
+ if (depth > 10) return strings;
385
+
386
+ if (typeof obj === 'string' && obj.length > 5) {
387
+ strings.push({ value: obj, path: path || 'root' });
388
+ } else if (Array.isArray(obj)) {
389
+ obj.forEach((item, i) => {
390
+ strings.push(...this._extractStrings(item, `${path}[${i}]`, depth + 1));
391
+ });
392
+ } else if (obj && typeof obj === 'object') {
393
+ for (const [key, value] of Object.entries(obj)) {
394
+ strings.push(...this._extractStrings(value, path ? `${path}.${key}` : key, depth + 1));
395
+ }
396
+ }
397
+
398
+ return strings;
399
+ }
400
+
401
+ /** @private */
402
+ _scanStrings(strings, source) {
403
+ const threats = [];
404
+
405
+ for (const { value, path } of strings) {
406
+ const result = scanText(value, { source: `${source}:${path}`, sensitivity: 'high' });
407
+ if (result.threats.length > 0) {
408
+ threats.push(...result.threats.map(t => ({
409
+ ...t,
410
+ dataPath: path,
411
+ description: `${t.description} (found in structured data at ${path})`
412
+ })));
413
+ }
414
+ }
415
+
416
+ if (threats.length > 0 && this.onDetection) {
417
+ this.onDetection({ threats, source, timestamp: Date.now() });
418
+ }
419
+
420
+ return { clean: threats.length === 0, threats };
421
+ }
422
+ }
423
+
424
+ module.exports = {
425
+ SteganographyDetector,
426
+ EncodingBruteforceDetector,
427
+ StructuredDataScanner,
428
+ STEGO_PATTERNS
429
+ };