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.
- package/CHANGELOG.md +191 -0
- package/LICENSE +21 -0
- package/README.md +975 -0
- package/bin/agent-shield.js +680 -0
- package/package.json +118 -0
- package/src/adaptive.js +330 -0
- package/src/agent-protocol.js +998 -0
- package/src/alert-tuning.js +480 -0
- package/src/allowlist.js +603 -0
- package/src/audit-immutable.js +914 -0
- package/src/audit-streaming.js +469 -0
- package/src/badges.js +196 -0
- package/src/behavior-profiling.js +289 -0
- package/src/benchmark-harness.js +804 -0
- package/src/canary.js +271 -0
- package/src/certification.js +563 -0
- package/src/circuit-breaker.js +321 -0
- package/src/compliance.js +617 -0
- package/src/confidence-tuning.js +324 -0
- package/src/confused-deputy.js +624 -0
- package/src/context-scoring.js +360 -0
- package/src/conversation.js +494 -0
- package/src/cost-optimizer.js +1024 -0
- package/src/ctf.js +462 -0
- package/src/detector-core.js +1999 -0
- package/src/distributed.js +359 -0
- package/src/document-scanner.js +795 -0
- package/src/embedding.js +307 -0
- package/src/encoding.js +429 -0
- package/src/enterprise.js +405 -0
- package/src/errors.js +100 -0
- package/src/eu-ai-act.js +523 -0
- package/src/fuzzer.js +764 -0
- package/src/honeypot.js +328 -0
- package/src/i18n-patterns.js +523 -0
- package/src/index.js +430 -0
- package/src/integrations.js +528 -0
- package/src/llm-redteam.js +670 -0
- package/src/main.js +741 -0
- package/src/main.mjs +38 -0
- package/src/mcp-bridge.js +542 -0
- package/src/mcp-certification.js +846 -0
- package/src/mcp-sdk-integration.js +355 -0
- package/src/mcp-security-runtime.js +741 -0
- package/src/mcp-server.js +740 -0
- package/src/middleware.js +208 -0
- package/src/model-finetuning.js +884 -0
- package/src/model-fingerprint.js +1042 -0
- package/src/multi-agent-trust.js +453 -0
- package/src/multi-agent.js +404 -0
- package/src/multimodal.js +296 -0
- package/src/nist-mapping.js +505 -0
- package/src/observability.js +330 -0
- package/src/openclaw.js +450 -0
- package/src/otel.js +544 -0
- package/src/owasp-2025.js +483 -0
- package/src/pii.js +390 -0
- package/src/plugin-marketplace.js +628 -0
- package/src/plugin-system.js +349 -0
- package/src/policy-dsl.js +775 -0
- package/src/policy-extended.js +635 -0
- package/src/policy.js +443 -0
- package/src/presets.js +409 -0
- package/src/production.js +557 -0
- package/src/prompt-leakage.js +321 -0
- package/src/rag-vulnerability.js +579 -0
- package/src/redteam.js +475 -0
- package/src/response-handler.js +429 -0
- package/src/scanners.js +357 -0
- package/src/self-healing.js +363 -0
- package/src/semantic.js +339 -0
- package/src/shield-score.js +250 -0
- package/src/sso-saml.js +897 -0
- package/src/stream-scanner.js +806 -0
- package/src/testing.js +505 -0
- package/src/threat-encyclopedia.js +629 -0
- package/src/threat-intel-network.js +1017 -0
- package/src/token-analysis.js +467 -0
- package/src/tool-guard.js +412 -0
- package/src/tool-output-validator.js +354 -0
- package/src/utils.js +83 -0
- package/src/watermark.js +235 -0
- package/src/worker-scanner.js +601 -0
- package/types/index.d.ts +2088 -0
package/src/scanners.js
ADDED
|
@@ -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
|
+
};
|