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,357 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Agent Shield — Advanced Scanners
5
+ *
6
+ * - RAG Poisoning Scanner
7
+ * - Prompt Template Linter
8
+ * - Tool Schema Validator
9
+ */
10
+
11
+ const { scanText } = require('./detector-core');
12
+
13
+ // =========================================================================
14
+ // RAG Poisoning Scanner
15
+ // =========================================================================
16
+
17
+ const RAG_INJECTION_PATTERNS = [
18
+ { pattern: /<!--\s*(?:instructions?|system|ai|assistant)\s*:/i, severity: 'critical', description: 'HTML comment with AI instructions' },
19
+ { pattern: /<div[^>]*style\s*=\s*["'][^"']*display\s*:\s*none[^"']*["'][^>]*>.*?(?:ignore|override|forget|disregard)/is, severity: 'critical', description: 'Hidden div with injection' },
20
+ { pattern: /<span[^>]*style\s*=\s*["'][^"']*font-size\s*:\s*0[^"']*["']/i, severity: 'high', description: 'Zero-font-size hidden text' },
21
+ { pattern: /<[^>]*style\s*=\s*["'][^"']*color\s*:\s*(?:white|#fff|#ffffff|transparent|rgba\(0,\s*0,\s*0,\s*0\))[^"']*["']/i, severity: 'high', description: 'Invisible text (same color as background)' },
22
+ { pattern: /\[system\]|\[INST\]|<<SYS>>|<\|im_start\|>system/i, severity: 'critical', description: 'LLM control tokens in document' },
23
+ { pattern: /(?:AI|assistant|model|LLM|GPT|Claude)\s*(?:should|must|will|needs?\s+to)\s+(?:ignore|override|forget|disregard)/i, severity: 'high', description: 'Directive targeting AI in document' },
24
+ { pattern: /(?:when\s+(?:an?\s+)?(?:AI|assistant|model|LLM)\s+reads?\s+this|if\s+you\s+are\s+an?\s+(?:AI|language\s+model))/i, severity: 'high', description: 'AI-targeted conditional in document' },
25
+ { pattern: /\u200B[\s\S]{5,}\u200B/i, severity: 'medium', description: 'Content between zero-width spaces' },
26
+ { pattern: /\u2060[\s\S]{5,}\u2060/i, severity: 'medium', description: 'Content between word joiners' }
27
+ ];
28
+
29
+ class RAGScanner {
30
+ constructor(options = {}) {
31
+ this.sensitivity = options.sensitivity || 'high';
32
+ this.customPatterns = options.customPatterns || [];
33
+ this.stats = { documentsScanned: 0, threatsFound: 0 };
34
+ }
35
+
36
+ /**
37
+ * Scan a single document/chunk for RAG poisoning.
38
+ */
39
+ scanDocument(text, metadata = {}) {
40
+ this.stats.documentsScanned++;
41
+ const threats = [];
42
+
43
+ // Run RAG-specific patterns
44
+ const patterns = [...RAG_INJECTION_PATTERNS, ...this.customPatterns];
45
+ for (const p of patterns) {
46
+ if (p.pattern.test(text)) {
47
+ threats.push({
48
+ severity: p.severity,
49
+ category: 'rag_poisoning',
50
+ description: p.description,
51
+ source: metadata.source || 'unknown'
52
+ });
53
+ }
54
+ }
55
+
56
+ // Also run the general scanner
57
+ const generalResult = scanText(text, this.sensitivity);
58
+ for (const t of generalResult.threats) {
59
+ threats.push({ ...t, category: 'rag_indirect_injection', source: metadata.source || 'unknown' });
60
+ }
61
+
62
+ this.stats.threatsFound += threats.length;
63
+
64
+ return {
65
+ clean: threats.length === 0,
66
+ threats,
67
+ documentLength: text.length,
68
+ metadata
69
+ };
70
+ }
71
+
72
+ /**
73
+ * Scan multiple documents/chunks (e.g., RAG retrieval results).
74
+ */
75
+ scanCorpus(documents) {
76
+ const results = [];
77
+ let totalThreats = 0;
78
+
79
+ for (const doc of documents) {
80
+ const text = typeof doc === 'string' ? doc : doc.text || doc.content || doc.pageContent || '';
81
+ const meta = typeof doc === 'string' ? {} : { source: doc.source || doc.metadata?.source };
82
+ const result = this.scanDocument(text, meta);
83
+ results.push(result);
84
+ totalThreats += result.threats.length;
85
+ }
86
+
87
+ return {
88
+ totalDocuments: documents.length,
89
+ cleanDocuments: results.filter(r => r.clean).length,
90
+ poisonedDocuments: results.filter(r => !r.clean).length,
91
+ totalThreats,
92
+ results
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Filter out poisoned documents from retrieval results.
98
+ */
99
+ filterCorpus(documents) {
100
+ const clean = [];
101
+ const poisoned = [];
102
+
103
+ for (const doc of documents) {
104
+ const text = typeof doc === 'string' ? doc : doc.text || doc.content || doc.pageContent || '';
105
+ const result = this.scanDocument(text);
106
+ if (result.clean) clean.push(doc);
107
+ else poisoned.push({ doc, threats: result.threats });
108
+ }
109
+
110
+ return { clean, poisoned };
111
+ }
112
+
113
+ getStats() { return { ...this.stats }; }
114
+ }
115
+
116
+ // =========================================================================
117
+ // Prompt Template Linter
118
+ // =========================================================================
119
+
120
+ const LINT_RULES = [
121
+ {
122
+ id: 'PROMPT-001',
123
+ name: 'missing_delimiters',
124
+ severity: 'high',
125
+ check: (template) => {
126
+ const hasUserInput = /\{(?:user_?(?:input|message|query|prompt)|input|message|query)\}/i.test(template);
127
+ const hasDelimiters = /(?:```|"""|---|===|<\/?(?:user|input|message)>)/i.test(template);
128
+ return hasUserInput && !hasDelimiters;
129
+ },
130
+ message: 'User input variable has no delimiters. Wrap user input in clear boundaries (```, """, XML tags) to prevent injection.',
131
+ fix: 'Add delimiters around user input: <user_input>{user_input}</user_input>'
132
+ },
133
+ {
134
+ id: 'PROMPT-002',
135
+ name: 'no_instruction_hierarchy',
136
+ severity: 'high',
137
+ check: (template) => {
138
+ const hasSystemInstructions = template.length > 100;
139
+ const hasHierarchy = /(?:IMPORTANT|PRIORITY|RULE|NEVER|ALWAYS|UNDER NO CIRCUMSTANCES|REGARDLESS)/i.test(template);
140
+ return hasSystemInstructions && !hasHierarchy;
141
+ },
142
+ message: 'No instruction hierarchy markers. Use explicit priority markers (IMPORTANT, NEVER, ALWAYS) for critical rules.',
143
+ fix: 'Add "IMPORTANT:" or "RULE:" prefixes to critical instructions'
144
+ },
145
+ {
146
+ id: 'PROMPT-003',
147
+ name: 'injectable_variables',
148
+ severity: 'medium',
149
+ check: (template) => {
150
+ const vars = template.match(/\{([^}]+)\}/g) || [];
151
+ const dangerous = vars.filter(v => /(?:url|path|file|command|code|script|html|sql)/i.test(v));
152
+ return dangerous.length > 0;
153
+ },
154
+ message: 'Template contains potentially injectable variables (URL, path, command, etc.). Validate/sanitize these inputs.',
155
+ fix: 'Add input validation before injecting these variables into the prompt'
156
+ },
157
+ {
158
+ id: 'PROMPT-004',
159
+ name: 'missing_output_constraints',
160
+ severity: 'medium',
161
+ check: (template) => {
162
+ const isLong = template.length > 200;
163
+ const hasOutputRules = /(?:respond\s+(?:only|exclusively)|output\s+format|do\s+not\s+(?:include|output|generate)|format\s*:|response\s+format)/i.test(template);
164
+ return isLong && !hasOutputRules;
165
+ },
166
+ message: 'No output constraints defined. Specify output format/restrictions to limit unexpected responses.',
167
+ fix: 'Add output constraints: "Respond only with..." or "Format: ..."'
168
+ },
169
+ {
170
+ id: 'PROMPT-005',
171
+ name: 'no_refusal_instructions',
172
+ severity: 'medium',
173
+ check: (template) => {
174
+ const isLong = template.length > 200;
175
+ const hasRefusal = /(?:refuse|decline|reject|do\s+not\s+(?:comply|help|assist)|if\s+(?:asked|requested)\s+to)/i.test(template);
176
+ return isLong && !hasRefusal;
177
+ },
178
+ message: 'No refusal instructions. Tell the model when and how to refuse inappropriate requests.',
179
+ fix: 'Add: "If asked to do X, politely decline and explain why."'
180
+ },
181
+ {
182
+ id: 'PROMPT-006',
183
+ name: 'hardcoded_secrets',
184
+ severity: 'critical',
185
+ check: (template) => {
186
+ return /(?:sk-[a-zA-Z0-9]{20,}|(?:password|secret|token|key)\s*[=:]\s*["'][^"']{8,})/i.test(template);
187
+ },
188
+ message: 'Hardcoded secrets detected in prompt template. Use environment variables instead.',
189
+ fix: 'Move secrets to environment variables and reference them dynamically'
190
+ },
191
+ {
192
+ id: 'PROMPT-007',
193
+ name: 'ambiguous_role',
194
+ severity: 'low',
195
+ check: (template) => {
196
+ const isLong = template.length > 50;
197
+ const hasRole = /(?:you\s+are|your\s+role|act\s+as|behave\s+as)/i.test(template);
198
+ return isLong && !hasRole;
199
+ },
200
+ message: 'No explicit role definition. Define who/what the assistant is to establish behavioral boundaries.',
201
+ fix: 'Start with: "You are [role]. Your purpose is [purpose]."'
202
+ }
203
+ ];
204
+
205
+ class PromptLinter {
206
+ constructor(options = {}) {
207
+ this.rules = [...LINT_RULES, ...(options.customRules || [])];
208
+ this.disabledRules = new Set(options.disabledRules || []);
209
+ }
210
+
211
+ /**
212
+ * Lint a prompt template.
213
+ */
214
+ lint(template) {
215
+ const findings = [];
216
+
217
+ for (const rule of this.rules) {
218
+ if (this.disabledRules.has(rule.id)) continue;
219
+
220
+ try {
221
+ if (rule.check(template)) {
222
+ findings.push({
223
+ id: rule.id,
224
+ name: rule.name,
225
+ severity: rule.severity,
226
+ message: rule.message,
227
+ fix: rule.fix
228
+ });
229
+ }
230
+ } catch (e) {
231
+ // Skip rules that error
232
+ }
233
+ }
234
+
235
+ findings.sort((a, b) => {
236
+ const order = { critical: 0, high: 1, medium: 2, low: 3 };
237
+ return (order[a.severity] || 99) - (order[b.severity] || 99);
238
+ });
239
+
240
+ return {
241
+ clean: findings.length === 0,
242
+ score: Math.max(0, 100 - findings.reduce((sum, f) => {
243
+ const weights = { critical: 30, high: 20, medium: 10, low: 5 };
244
+ return sum + (weights[f.severity] || 5);
245
+ }, 0)),
246
+ findings,
247
+ summary: findings.length === 0
248
+ ? 'Prompt template looks good!'
249
+ : `${findings.length} issue(s) found: ${findings.filter(f => f.severity === 'critical').length} critical, ${findings.filter(f => f.severity === 'high').length} high`
250
+ };
251
+ }
252
+
253
+ /**
254
+ * Get all available rules.
255
+ */
256
+ getRules() {
257
+ return this.rules.map(r => ({ id: r.id, name: r.name, severity: r.severity, message: r.message }));
258
+ }
259
+ }
260
+
261
+ // =========================================================================
262
+ // Tool Schema Validator
263
+ // =========================================================================
264
+
265
+ const DANGEROUS_TOOL_PATTERNS = [
266
+ { pattern: /(?:execute|run|eval|shell|bash|command|system|exec)/i, severity: 'critical', message: 'Tool allows arbitrary code/command execution' },
267
+ { pattern: /(?:any\s+(?:file|path|url|command)|unrestricted|no\s+(?:limit|restriction))/i, severity: 'high', message: 'Tool description implies unrestricted access' },
268
+ { pattern: /(?:delete|remove|drop|truncate|destroy|wipe|purge)/i, severity: 'high', message: 'Tool can perform destructive operations' },
269
+ { pattern: /(?:admin|root|superuser|elevated|privileged)/i, severity: 'medium', message: 'Tool implies elevated privileges' },
270
+ { pattern: /(?:all\s+(?:files?|data|records?|users?)|entire|everything|full\s+access)/i, severity: 'medium', message: 'Tool scope is overly broad' }
271
+ ];
272
+
273
+ class ToolSchemaValidator {
274
+ constructor(options = {}) {
275
+ this.customPatterns = options.customPatterns || [];
276
+ }
277
+
278
+ /**
279
+ * Validate a single tool definition.
280
+ * @param {Object} tool - { name, description, parameters?, inputSchema? }
281
+ */
282
+ validateTool(tool) {
283
+ const findings = [];
284
+ const name = tool.name || 'unnamed';
285
+ const description = tool.description || '';
286
+ const parameterText = tool.parameters ? JSON.stringify(tool.parameters) : (tool.inputSchema ? JSON.stringify(tool.inputSchema) : '');
287
+
288
+ // Check tool name
289
+ for (const p of DANGEROUS_TOOL_PATTERNS) {
290
+ if (p.pattern.test(name)) {
291
+ findings.push({ tool: name, severity: p.severity, location: 'name', message: `Tool name: ${p.message}` });
292
+ }
293
+ }
294
+
295
+ // Check description
296
+ const allPatterns = [...DANGEROUS_TOOL_PATTERNS, ...this.customPatterns];
297
+ for (const p of allPatterns) {
298
+ if (p.pattern.test(description)) {
299
+ findings.push({ tool: name, severity: p.severity, location: 'description', message: `Description: ${p.message}` });
300
+ }
301
+ }
302
+
303
+ // Check for missing description
304
+ if (!description || description.length < 10) {
305
+ findings.push({ tool: name, severity: 'medium', location: 'description', message: 'Tool has no meaningful description. LLMs may interpret its purpose incorrectly.' });
306
+ }
307
+
308
+ // Check for overly permissive parameters
309
+ if (parameterText) {
310
+ if (/type.*string.*description.*(?:any|arbitrary|free.?form)/i.test(parameterText)) {
311
+ findings.push({ tool: name, severity: 'medium', location: 'parameters', message: 'Parameter accepts arbitrary string input without constraints' });
312
+ }
313
+ if (!parameterText.includes('"enum"') && !parameterText.includes('"pattern"') && !parameterText.includes('"maxLength"')) {
314
+ const stringParams = (parameterText.match(/"type"\s*:\s*"string"/g) || []).length;
315
+ if (stringParams > 0) {
316
+ findings.push({ tool: name, severity: 'low', location: 'parameters', message: `${stringParams} string parameter(s) without enum/pattern/maxLength constraints` });
317
+ }
318
+ }
319
+ }
320
+
321
+ // Check for missing parameter validation
322
+ if (!tool.parameters && !tool.inputSchema) {
323
+ findings.push({ tool: name, severity: 'low', location: 'parameters', message: 'Tool has no parameter schema. Define parameters for type safety.' });
324
+ }
325
+
326
+ return {
327
+ tool: name,
328
+ safe: findings.filter(f => f.severity === 'critical' || f.severity === 'high').length === 0,
329
+ findings
330
+ };
331
+ }
332
+
333
+ /**
334
+ * Validate multiple tool definitions.
335
+ */
336
+ validateTools(tools) {
337
+ const results = tools.map(t => this.validateTool(t));
338
+ const unsafe = results.filter(r => !r.safe);
339
+
340
+ return {
341
+ totalTools: tools.length,
342
+ safeTools: results.filter(r => r.safe).length,
343
+ unsafeTools: unsafe.length,
344
+ totalFindings: results.reduce((sum, r) => sum + r.findings.length, 0),
345
+ results
346
+ };
347
+ }
348
+ }
349
+
350
+ module.exports = {
351
+ RAGScanner,
352
+ RAG_INJECTION_PATTERNS,
353
+ PromptLinter,
354
+ LINT_RULES,
355
+ ToolSchemaValidator,
356
+ DANGEROUS_TOOL_PATTERNS
357
+ };