ship-safe 3.1.0 → 4.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/README.md +200 -307
- package/cli/agents/api-fuzzer.js +224 -0
- package/cli/agents/auth-bypass-agent.js +326 -0
- package/cli/agents/base-agent.js +240 -0
- package/cli/agents/cicd-scanner.js +200 -0
- package/cli/agents/config-auditor.js +413 -0
- package/cli/agents/git-history-scanner.js +167 -0
- package/cli/agents/html-reporter.js +363 -0
- package/cli/agents/index.js +56 -0
- package/cli/agents/injection-tester.js +401 -0
- package/cli/agents/llm-redteam.js +251 -0
- package/cli/agents/mobile-scanner.js +225 -0
- package/cli/agents/orchestrator.js +152 -0
- package/cli/agents/policy-engine.js +149 -0
- package/cli/agents/recon-agent.js +196 -0
- package/cli/agents/sbom-generator.js +176 -0
- package/cli/agents/scoring-engine.js +207 -0
- package/cli/agents/ssrf-prober.js +130 -0
- package/cli/agents/supply-chain-agent.js +274 -0
- package/cli/bin/ship-safe.js +119 -2
- package/cli/commands/agent.js +606 -0
- package/cli/commands/audit.js +565 -0
- package/cli/commands/deps.js +447 -0
- package/cli/commands/fix.js +3 -3
- package/cli/commands/init.js +86 -3
- package/cli/commands/mcp.js +2 -2
- package/cli/commands/red-team.js +315 -0
- package/cli/commands/remediate.js +4 -4
- package/cli/commands/rotate.js +6 -6
- package/cli/commands/scan.js +64 -23
- package/cli/commands/score.js +446 -0
- package/cli/commands/watch.js +160 -0
- package/cli/index.js +40 -2
- package/cli/providers/llm-provider.js +288 -0
- package/cli/utils/entropy.js +6 -0
- package/cli/utils/output.js +42 -2
- package/cli/utils/patterns.js +393 -1
- package/package.json +19 -15
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* InjectionTester Agent
|
|
3
|
+
* ======================
|
|
4
|
+
*
|
|
5
|
+
* Detects injection vulnerabilities by tracing data flow patterns
|
|
6
|
+
* from user input to dangerous sinks.
|
|
7
|
+
*
|
|
8
|
+
* Covers: SQL Injection, NoSQL Injection, Command Injection,
|
|
9
|
+
* Code Injection, XSS, LDAP Injection, Template Injection,
|
|
10
|
+
* Header Injection, Path Traversal, Log Injection,
|
|
11
|
+
* GraphQL Injection, Open Redirect.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import path from 'path';
|
|
15
|
+
import { BaseAgent, createFinding } from './base-agent.js';
|
|
16
|
+
|
|
17
|
+
// =============================================================================
|
|
18
|
+
// INJECTION PATTERNS
|
|
19
|
+
// =============================================================================
|
|
20
|
+
|
|
21
|
+
const PATTERNS = [
|
|
22
|
+
// ── SQL Injection ──────────────────────────────────────────────────────────
|
|
23
|
+
{
|
|
24
|
+
rule: 'SQL_INJECTION_TEMPLATE_LITERAL',
|
|
25
|
+
title: 'SQL Injection via Template Literal',
|
|
26
|
+
regex: /`(?:SELECT|INSERT|UPDATE|DELETE|DROP\s+TABLE|ALTER\s+TABLE|TRUNCATE|CREATE|REPLACE|MERGE)[^`]*\$\{/gi,
|
|
27
|
+
severity: 'critical',
|
|
28
|
+
cwe: 'CWE-89',
|
|
29
|
+
owasp: 'A03:2021',
|
|
30
|
+
description: 'SQL query with interpolated template variable. Use parameterized queries ($1, ?) or an ORM.',
|
|
31
|
+
fix: 'Use parameterized queries: db.query("SELECT * FROM users WHERE id = $1", [userId])',
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
rule: 'SQL_INJECTION_CONCAT',
|
|
35
|
+
title: 'SQL Injection via String Concatenation',
|
|
36
|
+
regex: /["'](?:SELECT|INSERT|UPDATE|DELETE)\s+[^"']{4,}["']\s*\+/gi,
|
|
37
|
+
severity: 'high',
|
|
38
|
+
cwe: 'CWE-89',
|
|
39
|
+
owasp: 'A03:2021',
|
|
40
|
+
description: 'SQL built with string concatenation is vulnerable to injection. Use parameterized queries.',
|
|
41
|
+
fix: 'Replace string concat with parameterized query or ORM method',
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
rule: 'SQL_INJECTION_RAW',
|
|
45
|
+
title: 'Raw SQL Query (Prisma/Sequelize/Knex)',
|
|
46
|
+
regex: /(?:\$queryRaw|\.raw|knex\.raw)\s*\(\s*`[^`]*\$\{/gi,
|
|
47
|
+
severity: 'critical',
|
|
48
|
+
cwe: 'CWE-89',
|
|
49
|
+
owasp: 'A03:2021',
|
|
50
|
+
description: 'ORM raw query with interpolated values bypasses parameterization. Use tagged templates or bind parameters.',
|
|
51
|
+
fix: 'Prisma: use $queryRaw`...${Prisma.sql}...`; Sequelize: use bind parameters',
|
|
52
|
+
},
|
|
53
|
+
|
|
54
|
+
// ── NoSQL Injection ────────────────────────────────────────────────────────
|
|
55
|
+
{
|
|
56
|
+
rule: 'NOSQL_INJECTION_WHERE',
|
|
57
|
+
title: 'NoSQL Injection via $where',
|
|
58
|
+
regex: /\$where\s*:/g,
|
|
59
|
+
severity: 'high',
|
|
60
|
+
cwe: 'CWE-943',
|
|
61
|
+
owasp: 'A03:2021',
|
|
62
|
+
description: '$where in MongoDB executes JavaScript and is vulnerable to injection. Use standard query operators.',
|
|
63
|
+
fix: 'Replace $where with standard MongoDB operators ($eq, $gt, $regex, etc.)',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
rule: 'NOSQL_INJECTION_DYNAMIC',
|
|
67
|
+
title: 'NoSQL Injection via Dynamic Query',
|
|
68
|
+
regex: /\.find\(\s*(?:req\.|request\.|ctx\.)/g,
|
|
69
|
+
severity: 'high',
|
|
70
|
+
cwe: 'CWE-943',
|
|
71
|
+
owasp: 'A03:2021',
|
|
72
|
+
description: 'Passing request data directly to MongoDB find() enables NoSQL injection. Validate and whitelist query fields.',
|
|
73
|
+
fix: 'Validate input: only allow expected fields, cast types explicitly',
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
// ── Command Injection ──────────────────────────────────────────────────────
|
|
77
|
+
{
|
|
78
|
+
rule: 'CMD_INJECTION_EXEC_TEMPLATE',
|
|
79
|
+
title: 'Command Injection via exec() Template',
|
|
80
|
+
regex: /\bexec(?:Sync)?\s*\(\s*`[^`]*\$\{/g,
|
|
81
|
+
severity: 'critical',
|
|
82
|
+
cwe: 'CWE-78',
|
|
83
|
+
owasp: 'A03:2021',
|
|
84
|
+
description: 'Shell command with interpolated values enables command injection. Use execFile() with argument arrays.',
|
|
85
|
+
fix: 'Use execFile(cmd, [arg1, arg2]) instead of exec(`cmd ${arg}`)',
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
rule: 'CMD_INJECTION_EXEC_CONCAT',
|
|
89
|
+
title: 'Command Injection via exec() Concatenation',
|
|
90
|
+
regex: /\bexec(?:Sync)?\s*\(\s*["'][^"']*["']\s*\+/g,
|
|
91
|
+
severity: 'critical',
|
|
92
|
+
cwe: 'CWE-78',
|
|
93
|
+
owasp: 'A03:2021',
|
|
94
|
+
description: 'Shell command built with string concatenation enables injection. Use execFile() with arrays.',
|
|
95
|
+
fix: 'Use execFile(cmd, [arg1, arg2]) or spawn(cmd, args) without shell: true',
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
rule: 'CMD_INJECTION_SHELL_TRUE',
|
|
99
|
+
title: 'Command Injection via shell: true',
|
|
100
|
+
regex: /\bspawn(?:Sync)?\s*\([^)]*\bshell\s*:\s*true/g,
|
|
101
|
+
severity: 'high',
|
|
102
|
+
cwe: 'CWE-78',
|
|
103
|
+
owasp: 'A03:2021',
|
|
104
|
+
description: 'shell: true in spawn enables shell expansion and command injection. Pass args as array without shell.',
|
|
105
|
+
fix: 'Remove shell: true and pass arguments as an array',
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
rule: 'CMD_INJECTION_PYTHON_OS',
|
|
109
|
+
title: 'Command Injection via os.system/popen',
|
|
110
|
+
regex: /\b(?:os\.system|os\.popen|subprocess\.call|subprocess\.Popen)\s*\([^)]*(?:f['"]|\.format\(|\s*\+\s*)/g,
|
|
111
|
+
severity: 'critical',
|
|
112
|
+
cwe: 'CWE-78',
|
|
113
|
+
owasp: 'A03:2021',
|
|
114
|
+
description: 'Shell command with string formatting enables injection. Use subprocess.run() with args list.',
|
|
115
|
+
fix: 'Use subprocess.run([cmd, arg1, arg2], shell=False)',
|
|
116
|
+
},
|
|
117
|
+
|
|
118
|
+
// ── Code Injection ─────────────────────────────────────────────────────────
|
|
119
|
+
{
|
|
120
|
+
rule: 'CODE_INJECTION_EVAL',
|
|
121
|
+
title: 'Code Injection via eval()',
|
|
122
|
+
regex: /\beval\s*\(\s*(?:req\.|request\.|ctx\.|params|query|body|input|data|user)/g,
|
|
123
|
+
severity: 'critical',
|
|
124
|
+
cwe: 'CWE-94',
|
|
125
|
+
owasp: 'A03:2021',
|
|
126
|
+
description: 'eval() with user input executes arbitrary code. Never pass user data to eval.',
|
|
127
|
+
fix: 'Use JSON.parse() for data, a sandboxed interpreter, or restructure to avoid eval',
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
rule: 'CODE_INJECTION_EVAL_GENERIC',
|
|
131
|
+
title: 'Code Injection: eval() Usage',
|
|
132
|
+
regex: /\beval\s*\(/g,
|
|
133
|
+
severity: 'high',
|
|
134
|
+
cwe: 'CWE-94',
|
|
135
|
+
owasp: 'A03:2021',
|
|
136
|
+
confidence: 'medium',
|
|
137
|
+
description: 'eval() executes arbitrary code. Replace with JSON.parse(), Function, or a safer alternative.',
|
|
138
|
+
fix: 'Replace eval() with JSON.parse() or a domain-specific parser',
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
rule: 'CODE_INJECTION_NEW_FUNCTION',
|
|
142
|
+
title: 'Code Injection via new Function()',
|
|
143
|
+
regex: /\bnew\s+Function\s*\(/g,
|
|
144
|
+
severity: 'high',
|
|
145
|
+
cwe: 'CWE-94',
|
|
146
|
+
owasp: 'A03:2021',
|
|
147
|
+
description: 'new Function() is equivalent to eval(). Avoid dynamic code generation.',
|
|
148
|
+
fix: 'Refactor to avoid dynamic code generation',
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
rule: 'CODE_INJECTION_VM',
|
|
152
|
+
title: 'Code Injection via vm.runInNewContext()',
|
|
153
|
+
regex: /\bvm\.(?:runInNewContext|runInThisContext|compileFunction)\s*\(/g,
|
|
154
|
+
severity: 'high',
|
|
155
|
+
cwe: 'CWE-94',
|
|
156
|
+
owasp: 'A03:2021',
|
|
157
|
+
description: 'Node.js vm module does not provide security isolation. Use vm2 or isolated-vm for untrusted code.',
|
|
158
|
+
fix: 'Use isolated-vm or a proper sandbox for untrusted code execution',
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
// ── XSS ────────────────────────────────────────────────────────────────────
|
|
162
|
+
{
|
|
163
|
+
rule: 'XSS_DANGEROUS_HTML',
|
|
164
|
+
title: 'XSS via dangerouslySetInnerHTML',
|
|
165
|
+
regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{/g,
|
|
166
|
+
severity: 'high',
|
|
167
|
+
cwe: 'CWE-79',
|
|
168
|
+
owasp: 'A03:2021',
|
|
169
|
+
description: 'dangerouslySetInnerHTML can introduce XSS if the value contains user input.',
|
|
170
|
+
fix: 'Sanitize with DOMPurify: dangerouslySetInnerHTML={{__html: DOMPurify.sanitize(html)}}',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
rule: 'XSS_INNERHTML',
|
|
174
|
+
title: 'XSS via innerHTML Assignment',
|
|
175
|
+
regex: /\.innerHTML\s*=\s*(?!['"]<)/g,
|
|
176
|
+
severity: 'high',
|
|
177
|
+
cwe: 'CWE-79',
|
|
178
|
+
owasp: 'A03:2021',
|
|
179
|
+
description: 'innerHTML with dynamic data leads to XSS. Use textContent or DOMPurify.',
|
|
180
|
+
fix: 'Use element.textContent for text, or DOMPurify.sanitize() for HTML',
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
rule: 'XSS_DOCUMENT_WRITE',
|
|
184
|
+
title: 'XSS via document.write()',
|
|
185
|
+
regex: /\bdocument\.write(?:ln)?\s*\(/g,
|
|
186
|
+
severity: 'medium',
|
|
187
|
+
cwe: 'CWE-79',
|
|
188
|
+
owasp: 'A03:2021',
|
|
189
|
+
description: 'document.write() is deprecated and can introduce XSS. Use DOM manipulation.',
|
|
190
|
+
fix: 'Use createElement/appendChild or textContent instead',
|
|
191
|
+
},
|
|
192
|
+
{
|
|
193
|
+
rule: 'XSS_OUTERHTML',
|
|
194
|
+
title: 'XSS via outerHTML Assignment',
|
|
195
|
+
regex: /\.outerHTML\s*=/g,
|
|
196
|
+
severity: 'high',
|
|
197
|
+
cwe: 'CWE-79',
|
|
198
|
+
owasp: 'A03:2021',
|
|
199
|
+
description: 'outerHTML with dynamic content enables XSS. Sanitize with DOMPurify.',
|
|
200
|
+
fix: 'Sanitize HTML with DOMPurify before assigning to outerHTML',
|
|
201
|
+
},
|
|
202
|
+
{
|
|
203
|
+
rule: 'XSS_JQUERY_HTML',
|
|
204
|
+
title: 'XSS via jQuery .html()',
|
|
205
|
+
regex: /\$\([^)]+\)\.html\s*\(\s*(?!['"])/g,
|
|
206
|
+
severity: 'high',
|
|
207
|
+
cwe: 'CWE-79',
|
|
208
|
+
owasp: 'A03:2021',
|
|
209
|
+
description: 'jQuery .html() with dynamic data enables XSS. Use .text() or sanitize.',
|
|
210
|
+
fix: 'Use .text() for plain text or sanitize HTML with DOMPurify before .html()',
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
rule: 'XSS_V_HTML',
|
|
214
|
+
title: 'XSS via Vue v-html Directive',
|
|
215
|
+
regex: /v-html\s*=\s*["']/g,
|
|
216
|
+
severity: 'high',
|
|
217
|
+
cwe: 'CWE-79',
|
|
218
|
+
owasp: 'A03:2021',
|
|
219
|
+
description: 'Vue v-html renders raw HTML and is vulnerable to XSS. Sanitize before rendering.',
|
|
220
|
+
fix: 'Sanitize with DOMPurify or use v-text for plain text',
|
|
221
|
+
},
|
|
222
|
+
|
|
223
|
+
// ── Path Traversal ─────────────────────────────────────────────────────────
|
|
224
|
+
{
|
|
225
|
+
rule: 'PATH_TRAVERSAL_FS',
|
|
226
|
+
title: 'Path Traversal in File Operations',
|
|
227
|
+
regex: /(?:readFile|readFileSync|createReadStream|writeFile|writeFileSync|unlink|unlinkSync|stat|statSync)\s*\(\s*(?:req\.|request\.|ctx\.|params|query|`[^`]*\$\{)/g,
|
|
228
|
+
severity: 'critical',
|
|
229
|
+
cwe: 'CWE-22',
|
|
230
|
+
owasp: 'A01:2021',
|
|
231
|
+
description: 'User input in file path enables directory traversal (../../etc/passwd). Validate and restrict paths.',
|
|
232
|
+
fix: 'Use path.resolve() + validate that resolved path starts with allowed directory',
|
|
233
|
+
},
|
|
234
|
+
{
|
|
235
|
+
rule: 'PATH_TRAVERSAL_DOTDOT',
|
|
236
|
+
title: 'Path Traversal: No ../ Validation',
|
|
237
|
+
regex: /path\.join\s*\([^)]*(?:req\.|request\.|ctx\.|params|query|body)/g,
|
|
238
|
+
severity: 'high',
|
|
239
|
+
cwe: 'CWE-22',
|
|
240
|
+
owasp: 'A01:2021',
|
|
241
|
+
description: 'path.join() with user input without ../ validation enables traversal.',
|
|
242
|
+
fix: 'After path.join, verify: if (!resolvedPath.startsWith(allowedDir)) throw new Error()',
|
|
243
|
+
},
|
|
244
|
+
|
|
245
|
+
// ── Template Injection ─────────────────────────────────────────────────────
|
|
246
|
+
{
|
|
247
|
+
rule: 'TEMPLATE_INJECTION_EJS',
|
|
248
|
+
title: 'Server-Side Template Injection (EJS)',
|
|
249
|
+
regex: /ejs\.render\s*\(\s*(?:req\.|request\.|ctx\.|body|query|params)/g,
|
|
250
|
+
severity: 'critical',
|
|
251
|
+
cwe: 'CWE-94',
|
|
252
|
+
owasp: 'A03:2021',
|
|
253
|
+
description: 'Passing user input as an EJS template enables server-side template injection (SSTI).',
|
|
254
|
+
fix: 'Render from template files, pass user data only as template variables',
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
rule: 'TEMPLATE_INJECTION_UNESCAPED',
|
|
258
|
+
title: 'Unescaped Template Output',
|
|
259
|
+
regex: /<%[-=]?\s*(?:req\.|request\.|body|query|params)/g,
|
|
260
|
+
severity: 'high',
|
|
261
|
+
cwe: 'CWE-79',
|
|
262
|
+
owasp: 'A03:2021',
|
|
263
|
+
description: 'Unescaped template output with user data enables XSS. Use escaped output.',
|
|
264
|
+
fix: 'Use <%- sanitize(userInput) %> or escape before rendering',
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
// ── Header Injection ───────────────────────────────────────────────────────
|
|
268
|
+
{
|
|
269
|
+
rule: 'HEADER_INJECTION',
|
|
270
|
+
title: 'HTTP Header Injection',
|
|
271
|
+
regex: /(?:setHeader|writeHead|header)\s*\([^,]*,\s*(?:req\.|request\.|ctx\.|body|query|params)/g,
|
|
272
|
+
severity: 'high',
|
|
273
|
+
cwe: 'CWE-113',
|
|
274
|
+
owasp: 'A03:2021',
|
|
275
|
+
description: 'User input in HTTP headers enables header injection / response splitting.',
|
|
276
|
+
fix: 'Validate and sanitize: strip newlines (\\r\\n) from header values',
|
|
277
|
+
},
|
|
278
|
+
|
|
279
|
+
// ── Open Redirect ──────────────────────────────────────────────────────────
|
|
280
|
+
{
|
|
281
|
+
rule: 'OPEN_REDIRECT',
|
|
282
|
+
title: 'Open Redirect',
|
|
283
|
+
regex: /(?:res\.redirect|redirect|location\.href|window\.location)\s*(?:\(|=)\s*(?:req\.|request\.|ctx\.|query|params)/g,
|
|
284
|
+
severity: 'medium',
|
|
285
|
+
cwe: 'CWE-601',
|
|
286
|
+
owasp: 'A01:2021',
|
|
287
|
+
description: 'Redirecting to user-supplied URL enables phishing via open redirect.',
|
|
288
|
+
fix: 'Validate redirect URL is relative or matches an allowlist of trusted domains',
|
|
289
|
+
},
|
|
290
|
+
|
|
291
|
+
// ── Log Injection ──────────────────────────────────────────────────────────
|
|
292
|
+
{
|
|
293
|
+
rule: 'LOG_INJECTION',
|
|
294
|
+
title: 'Log Injection',
|
|
295
|
+
regex: /(?:console\.log|logger\.\w+|log\.\w+)\s*\(\s*`[^`]*\$\{(?:req\.|request\.|ctx\.|body|query|params)/g,
|
|
296
|
+
severity: 'medium',
|
|
297
|
+
cwe: 'CWE-117',
|
|
298
|
+
owasp: 'A09:2021',
|
|
299
|
+
description: 'Unsanitized user input in logs enables log forging and injection attacks.',
|
|
300
|
+
fix: 'Sanitize user input before logging: strip control characters and newlines',
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
// ── Regex DoS ──────────────────────────────────────────────────────────────
|
|
304
|
+
{
|
|
305
|
+
rule: 'REDOS',
|
|
306
|
+
title: 'Regular Expression DoS (ReDoS)',
|
|
307
|
+
regex: /new\s+RegExp\s*\(\s*(?:req\.|request\.|ctx\.|body|query|params|input|user)/g,
|
|
308
|
+
severity: 'high',
|
|
309
|
+
cwe: 'CWE-1333',
|
|
310
|
+
owasp: 'A03:2021',
|
|
311
|
+
description: 'User-controlled regex can cause catastrophic backtracking (ReDoS). Validate or use RE2.',
|
|
312
|
+
fix: 'Use the re2 package for user-supplied patterns, or validate regex complexity',
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
// ── Prototype Pollution ────────────────────────────────────────────────────
|
|
316
|
+
{
|
|
317
|
+
rule: 'PROTOTYPE_POLLUTION',
|
|
318
|
+
title: 'Prototype Pollution',
|
|
319
|
+
regex: /(?:Object\.assign|_\.merge|_\.extend|_\.defaultsDeep|lodash\.merge)\s*\(\s*(?:\{\}|[a-zA-Z]+),\s*(?:req\.|request\.|ctx\.|body|query|params|input|data)/g,
|
|
320
|
+
severity: 'high',
|
|
321
|
+
cwe: 'CWE-1321',
|
|
322
|
+
owasp: 'A03:2021',
|
|
323
|
+
description: 'Merging user input into objects can pollute Object.prototype. Validate input keys.',
|
|
324
|
+
fix: 'Validate keys against an allowlist, or use Object.create(null) as target',
|
|
325
|
+
},
|
|
326
|
+
|
|
327
|
+
// ── XXE ────────────────────────────────────────────────────────────────────
|
|
328
|
+
{
|
|
329
|
+
rule: 'XXE_PARSER',
|
|
330
|
+
title: 'XML External Entity (XXE) Injection',
|
|
331
|
+
regex: /(?:xml2js|libxmljs|DOMParser|parseString|parseXML)\s*(?:\.\w+\s*)?\(/g,
|
|
332
|
+
severity: 'high',
|
|
333
|
+
cwe: 'CWE-611',
|
|
334
|
+
owasp: 'A05:2017',
|
|
335
|
+
confidence: 'medium',
|
|
336
|
+
description: 'XML parsers with default settings may be vulnerable to XXE. Disable external entity processing.',
|
|
337
|
+
fix: 'Disable DTDs and external entities in parser configuration',
|
|
338
|
+
},
|
|
339
|
+
|
|
340
|
+
// ── Insecure Deserialization ────────────────────────────────────────────────
|
|
341
|
+
{
|
|
342
|
+
rule: 'UNSAFE_DESERIALIZE_PICKLE',
|
|
343
|
+
title: 'Unsafe Deserialization: pickle',
|
|
344
|
+
regex: /\bpickle\.loads?\s*\(/g,
|
|
345
|
+
severity: 'critical',
|
|
346
|
+
cwe: 'CWE-502',
|
|
347
|
+
owasp: 'A08:2021',
|
|
348
|
+
description: 'pickle.loads() on untrusted data enables arbitrary code execution.',
|
|
349
|
+
fix: 'Use JSON, msgpack, or protobuf for untrusted data serialization',
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
rule: 'UNSAFE_DESERIALIZE_YAML',
|
|
353
|
+
title: 'Unsafe Deserialization: yaml.load()',
|
|
354
|
+
regex: /\byaml\.load\s*\(\s*(?!.*Loader\s*=\s*yaml\.SafeLoader)/g,
|
|
355
|
+
severity: 'high',
|
|
356
|
+
cwe: 'CWE-502',
|
|
357
|
+
owasp: 'A08:2021',
|
|
358
|
+
description: 'yaml.load() without SafeLoader can execute arbitrary Python code.',
|
|
359
|
+
fix: 'Use yaml.safe_load() or yaml.load(data, Loader=yaml.SafeLoader)',
|
|
360
|
+
},
|
|
361
|
+
{
|
|
362
|
+
rule: 'UNSAFE_DESERIALIZE_UNSERIALIZE',
|
|
363
|
+
title: 'Unsafe Deserialization: PHP unserialize()',
|
|
364
|
+
regex: /\bunserialize\s*\(\s*\$/g,
|
|
365
|
+
severity: 'critical',
|
|
366
|
+
cwe: 'CWE-502',
|
|
367
|
+
owasp: 'A08:2021',
|
|
368
|
+
description: 'PHP unserialize() with user input enables object injection attacks.',
|
|
369
|
+
fix: 'Use json_decode() instead, or validate input with allowed_classes option',
|
|
370
|
+
},
|
|
371
|
+
];
|
|
372
|
+
|
|
373
|
+
// =============================================================================
|
|
374
|
+
// INJECTION TESTER AGENT
|
|
375
|
+
// =============================================================================
|
|
376
|
+
|
|
377
|
+
export class InjectionTester extends BaseAgent {
|
|
378
|
+
constructor() {
|
|
379
|
+
super('InjectionTester', 'Detect injection vulnerabilities across all classes', 'injection');
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
async analyze(context) {
|
|
383
|
+
const { rootPath, files } = context;
|
|
384
|
+
const codeFiles = files.filter(f => {
|
|
385
|
+
const ext = path.extname(f).toLowerCase();
|
|
386
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs',
|
|
387
|
+
'.py', '.rb', '.php', '.go', '.java'].includes(ext);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
let findings = [];
|
|
391
|
+
|
|
392
|
+
for (const file of codeFiles) {
|
|
393
|
+
const fileFindings = this.scanFileWithPatterns(file, PATTERNS);
|
|
394
|
+
findings = findings.concat(fileFindings);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
return findings;
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
export default InjectionTester;
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LLMRedTeam Agent
|
|
3
|
+
* =================
|
|
4
|
+
*
|
|
5
|
+
* AI/LLM security testing based on OWASP LLM Top 10 2025.
|
|
6
|
+
* Detects prompt injection vulnerabilities, system prompt leakage,
|
|
7
|
+
* unsafe LLM output handling, excessive agency, and more.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { BaseAgent } from './base-agent.js';
|
|
12
|
+
|
|
13
|
+
const PATTERNS = [
|
|
14
|
+
// ── LLM01: Prompt Injection ────────────────────────────────────────────────
|
|
15
|
+
{
|
|
16
|
+
rule: 'LLM_PROMPT_INJECTION_NO_SANITIZE',
|
|
17
|
+
title: 'Prompt Injection: No Input Sanitization',
|
|
18
|
+
regex: /(?:messages|prompt|content)\s*[:=]\s*(?:`[^`]*\$\{(?:req\.|request\.|body|query|params|input|user)|.*\+\s*(?:req\.|request\.|body|query|params|input|user))/g,
|
|
19
|
+
severity: 'high',
|
|
20
|
+
cwe: 'CWE-77',
|
|
21
|
+
owasp: 'LLM01',
|
|
22
|
+
description: 'User input concatenated directly into LLM prompt without sanitization enables prompt injection.',
|
|
23
|
+
fix: 'Sanitize user input, use structured messages, separate system/user content clearly',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
rule: 'LLM_SYSTEM_USER_CONCAT',
|
|
27
|
+
title: 'Prompt Injection: System + User Concatenation',
|
|
28
|
+
regex: /(?:system|systemPrompt|system_prompt)\s*[:=].*(?:\+\s*(?:user|input|query|message)|`[^`]*\$\{)/g,
|
|
29
|
+
severity: 'critical',
|
|
30
|
+
cwe: 'CWE-77',
|
|
31
|
+
owasp: 'LLM01',
|
|
32
|
+
description: 'System prompt concatenated with user input. User can override system instructions.',
|
|
33
|
+
fix: 'Use separate message roles: [{role:"system", content: systemPrompt}, {role:"user", content: userInput}]',
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
// ── LLM02: Sensitive Information Disclosure ────────────────────────────────
|
|
37
|
+
{
|
|
38
|
+
rule: 'LLM_SECRET_IN_PROMPT',
|
|
39
|
+
title: 'Sensitive Data in LLM Prompt',
|
|
40
|
+
regex: /(?:system|prompt|content)\s*[:=].*(?:API_KEY|api_key|SECRET|PASSWORD|TOKEN|PRIVATE_KEY|DATABASE_URL)/g,
|
|
41
|
+
severity: 'critical',
|
|
42
|
+
cwe: 'CWE-200',
|
|
43
|
+
owasp: 'LLM02',
|
|
44
|
+
description: 'Sensitive data (secrets, keys) included in LLM prompt. Data may be logged or leaked.',
|
|
45
|
+
fix: 'Never include real credentials in prompts. Use placeholder references instead.',
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
rule: 'LLM_NO_OUTPUT_FILTER',
|
|
49
|
+
title: 'LLM Output Without Filtering',
|
|
50
|
+
regex: /(?:completion|response|result|output)(?:\.\w+)*\.(?:content|text|message)\s*(?:\)|;)/g,
|
|
51
|
+
severity: 'medium',
|
|
52
|
+
cwe: 'CWE-200',
|
|
53
|
+
owasp: 'LLM02',
|
|
54
|
+
confidence: 'low',
|
|
55
|
+
description: 'LLM output used directly without filtering. May contain sensitive info or hallucinations.',
|
|
56
|
+
fix: 'Filter LLM output before displaying: remove PII, validate against expected format',
|
|
57
|
+
},
|
|
58
|
+
|
|
59
|
+
// ── LLM05: Improper Output Handling ────────────────────────────────────────
|
|
60
|
+
{
|
|
61
|
+
rule: 'LLM_OUTPUT_TO_EVAL',
|
|
62
|
+
title: 'LLM Output to eval()/Function()',
|
|
63
|
+
regex: /eval\s*\(\s*(?:completion|response|result|output|generated|llm|ai|gpt|claude)/gi,
|
|
64
|
+
severity: 'critical',
|
|
65
|
+
cwe: 'CWE-94',
|
|
66
|
+
owasp: 'LLM05',
|
|
67
|
+
description: 'LLM output passed to eval() enables arbitrary code execution via prompt injection.',
|
|
68
|
+
fix: 'Never eval() LLM output. Parse as JSON with try/catch, or use a sandboxed interpreter.',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
rule: 'LLM_OUTPUT_TO_SQL',
|
|
72
|
+
title: 'LLM Output in SQL Query',
|
|
73
|
+
regex: /(?:query|execute|raw)\s*\(\s*(?:completion|response|result|output|generated|llm|ai|gpt|claude)/gi,
|
|
74
|
+
severity: 'critical',
|
|
75
|
+
cwe: 'CWE-89',
|
|
76
|
+
owasp: 'LLM05',
|
|
77
|
+
description: 'LLM-generated text used in SQL query. Attacker can inject SQL via prompt injection.',
|
|
78
|
+
fix: 'Never use LLM output in raw SQL. Validate against expected query patterns.',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
rule: 'LLM_OUTPUT_TO_HTML',
|
|
82
|
+
title: 'LLM Output Rendered as HTML',
|
|
83
|
+
regex: /(?:innerHTML|dangerouslySetInnerHTML|v-html)\s*=\s*(?:.*(?:completion|response|result|output|generated|llm|ai|gpt|claude))/gi,
|
|
84
|
+
severity: 'high',
|
|
85
|
+
cwe: 'CWE-79',
|
|
86
|
+
owasp: 'LLM05',
|
|
87
|
+
description: 'LLM output rendered as unescaped HTML enables XSS via prompt injection.',
|
|
88
|
+
fix: 'Render LLM output as text, or sanitize with DOMPurify before HTML rendering.',
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
rule: 'LLM_OUTPUT_TO_SHELL',
|
|
92
|
+
title: 'LLM Output in Shell Command',
|
|
93
|
+
regex: /(?:exec|spawn|system|popen)\s*\(\s*(?:completion|response|result|output|generated|llm|ai|gpt|claude)/gi,
|
|
94
|
+
severity: 'critical',
|
|
95
|
+
cwe: 'CWE-78',
|
|
96
|
+
owasp: 'LLM05',
|
|
97
|
+
description: 'LLM output used in shell command enables RCE via prompt injection.',
|
|
98
|
+
fix: 'Never pass LLM output to shell. Use a strict allowlist of allowed commands.',
|
|
99
|
+
},
|
|
100
|
+
|
|
101
|
+
// ── LLM06: Excessive Agency ────────────────────────────────────────────────
|
|
102
|
+
{
|
|
103
|
+
rule: 'LLM_TOOL_NO_CONFIRM',
|
|
104
|
+
title: 'LLM Tool Use Without Confirmation',
|
|
105
|
+
regex: /(?:tools|functions|function_call)\s*[:=]\s*\[.*(?:write|delete|update|create|send|execute|deploy|modify)/gi,
|
|
106
|
+
severity: 'high',
|
|
107
|
+
cwe: 'CWE-862',
|
|
108
|
+
owasp: 'LLM06',
|
|
109
|
+
confidence: 'medium',
|
|
110
|
+
description: 'LLM given tools with side effects (write/delete/send) without human confirmation.',
|
|
111
|
+
fix: 'Require human approval for destructive actions. Implement an approval workflow.',
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
rule: 'LLM_DB_WRITE_ACCESS',
|
|
115
|
+
title: 'LLM Has Database Write Access',
|
|
116
|
+
regex: /(?:tool|function).*(?:INSERT|UPDATE|DELETE|DROP|CREATE|ALTER).*(?:sql|query|database|db)/gi,
|
|
117
|
+
severity: 'critical',
|
|
118
|
+
cwe: 'CWE-862',
|
|
119
|
+
owasp: 'LLM06',
|
|
120
|
+
description: 'LLM can write to database. Prompt injection could corrupt or destroy data.',
|
|
121
|
+
fix: 'Give LLM read-only database access. Require human approval for writes.',
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
rule: 'LLM_FILE_WRITE',
|
|
125
|
+
title: 'LLM Has File System Write Access',
|
|
126
|
+
regex: /(?:tool|function).*(?:writeFile|fs\.write|unlink|rmdir|mkdir)/gi,
|
|
127
|
+
severity: 'critical',
|
|
128
|
+
cwe: 'CWE-862',
|
|
129
|
+
owasp: 'LLM06',
|
|
130
|
+
description: 'LLM can write/delete files. Prompt injection could modify or destroy files.',
|
|
131
|
+
fix: 'Restrict LLM file access to a sandboxed directory with read-only permissions.',
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
// ── LLM07: System Prompt Leakage ───────────────────────────────────────────
|
|
135
|
+
{
|
|
136
|
+
rule: 'LLM_SYSTEM_PROMPT_CLIENT',
|
|
137
|
+
title: 'System Prompt Exposed to Client',
|
|
138
|
+
regex: /(?:systemPrompt|system_prompt|SYSTEM_PROMPT)\s*[:=]\s*["'`]/g,
|
|
139
|
+
severity: 'high',
|
|
140
|
+
cwe: 'CWE-200',
|
|
141
|
+
owasp: 'LLM07',
|
|
142
|
+
confidence: 'medium',
|
|
143
|
+
description: 'System prompt hardcoded in code. If client-side, users can extract it.',
|
|
144
|
+
fix: 'Keep system prompts server-side only. Load from environment variables or config.',
|
|
145
|
+
},
|
|
146
|
+
|
|
147
|
+
// ── LLM10: Unbounded Consumption ───────────────────────────────────────────
|
|
148
|
+
{
|
|
149
|
+
rule: 'LLM_NO_TOKEN_LIMIT',
|
|
150
|
+
title: 'LLM Call Without Token Limit',
|
|
151
|
+
regex: /(?:openai|anthropic|ai)\.\w+\.create\s*\(\s*\{(?![\s\S]*max_tokens)[\s\S]*?\}/g,
|
|
152
|
+
severity: 'medium',
|
|
153
|
+
cwe: 'CWE-770',
|
|
154
|
+
owasp: 'LLM10',
|
|
155
|
+
confidence: 'low',
|
|
156
|
+
description: 'LLM API call without max_tokens limit. Could generate excessive output and costs.',
|
|
157
|
+
fix: 'Set max_tokens in API call to limit response size and costs',
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
rule: 'LLM_NO_RATE_LIMIT',
|
|
161
|
+
title: 'LLM Endpoint Without Rate Limiting',
|
|
162
|
+
regex: /(?:\/api\/.*(?:chat|complete|generate|ai|llm|gpt|claude)|\/chat|\/generate)\s*['"]/gi,
|
|
163
|
+
severity: 'medium',
|
|
164
|
+
cwe: 'CWE-770',
|
|
165
|
+
owasp: 'LLM10',
|
|
166
|
+
confidence: 'low',
|
|
167
|
+
description: 'AI endpoint without rate limiting. Users could rack up API costs.',
|
|
168
|
+
fix: 'Add rate limiting per user: express-rate-limit, @upstash/ratelimit, etc.',
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
rule: 'LLM_NO_COST_LIMIT',
|
|
172
|
+
title: 'LLM Usage Without Cost Controls',
|
|
173
|
+
regex: /(?:OPENAI|ANTHROPIC|AI)_(?:API_KEY|KEY).*(?!(?:budget|limit|cap|max_cost|spending))/gi,
|
|
174
|
+
severity: 'medium',
|
|
175
|
+
cwe: 'CWE-770',
|
|
176
|
+
owasp: 'LLM10',
|
|
177
|
+
confidence: 'low',
|
|
178
|
+
description: 'AI API usage without cost controls. Set spending limits on your provider dashboard.',
|
|
179
|
+
fix: 'Configure spending limits in OpenAI/Anthropic dashboard. Add per-user token budgets.',
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// ── LLM03: Supply Chain ────────────────────────────────────────────────────
|
|
183
|
+
{
|
|
184
|
+
rule: 'LLM_UNVERIFIED_MODEL',
|
|
185
|
+
title: 'Unverified Model Download',
|
|
186
|
+
regex: /(?:from_pretrained|AutoModel|pipeline)\s*\(\s*["'][^"']+\/[^"']+["']/g,
|
|
187
|
+
severity: 'medium',
|
|
188
|
+
cwe: 'CWE-829',
|
|
189
|
+
owasp: 'LLM03',
|
|
190
|
+
confidence: 'low',
|
|
191
|
+
description: 'Loading model from Hugging Face without verification. Model could contain backdoors.',
|
|
192
|
+
fix: 'Verify model hash, use models from trusted organizations, scan for malicious code',
|
|
193
|
+
},
|
|
194
|
+
|
|
195
|
+
// ── LLM08: Vector/Embedding Weaknesses ─────────────────────────────────────
|
|
196
|
+
{
|
|
197
|
+
rule: 'LLM_RAG_NO_VALIDATION',
|
|
198
|
+
title: 'RAG Pipeline Without Input Validation',
|
|
199
|
+
regex: /(?:embed|embedding|vector|similarity_search|query)\s*\(\s*(?:req\.|request\.|body|query|params|input|user)/g,
|
|
200
|
+
severity: 'medium',
|
|
201
|
+
cwe: 'CWE-20',
|
|
202
|
+
owasp: 'LLM08',
|
|
203
|
+
description: 'User input passed directly to vector search/embedding without validation.',
|
|
204
|
+
fix: 'Validate and sanitize input before embedding. Limit query length.',
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
rule: 'LLM_RAG_NO_ACCESS_CONTROL',
|
|
208
|
+
title: 'RAG Without Access Control',
|
|
209
|
+
regex: /(?:pinecone|chroma|weaviate|qdrant|milvus).*(?:query|search|similarity)\s*\(/g,
|
|
210
|
+
severity: 'medium',
|
|
211
|
+
cwe: 'CWE-862',
|
|
212
|
+
owasp: 'LLM08',
|
|
213
|
+
confidence: 'low',
|
|
214
|
+
description: 'Vector database query without access control. Users may access other users\' data.',
|
|
215
|
+
fix: 'Add namespace/tenant filtering: filter by userId in vector DB queries',
|
|
216
|
+
},
|
|
217
|
+
|
|
218
|
+
// ── Prompt Injection Patterns (content-level detection) ────────────────────
|
|
219
|
+
{
|
|
220
|
+
rule: 'PROMPT_INJECTION_PATTERN',
|
|
221
|
+
title: 'Known Prompt Injection Pattern',
|
|
222
|
+
regex: /(?:ignore\s+(?:all\s+)?previous\s+instructions|disregard\s+(?:all\s+)?(?:previous|prior)|you\s+are\s+now\s+DAN|system\s*prompt|jailbreak|bypass\s+(?:your|the)\s+(?:rules|instructions|guidelines))/gi,
|
|
223
|
+
severity: 'high',
|
|
224
|
+
cwe: 'CWE-77',
|
|
225
|
+
owasp: 'LLM01',
|
|
226
|
+
description: 'Known prompt injection pattern detected in code. Ensure this is for testing only.',
|
|
227
|
+
fix: 'If in test data, add # ship-safe-ignore. If in user-facing code, add input filtering.',
|
|
228
|
+
},
|
|
229
|
+
];
|
|
230
|
+
|
|
231
|
+
export class LLMRedTeam extends BaseAgent {
|
|
232
|
+
constructor() {
|
|
233
|
+
super('LLMRedTeam', 'AI/LLM security audit based on OWASP LLM Top 10', 'llm');
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async analyze(context) {
|
|
237
|
+
const { files } = context;
|
|
238
|
+
const codeFiles = files.filter(f => {
|
|
239
|
+
const ext = path.extname(f).toLowerCase();
|
|
240
|
+
return ['.js', '.jsx', '.ts', '.tsx', '.mjs', '.cjs', '.py', '.rb'].includes(ext);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
let findings = [];
|
|
244
|
+
for (const file of codeFiles) {
|
|
245
|
+
findings = findings.concat(this.scanFileWithPatterns(file, PATTERNS));
|
|
246
|
+
}
|
|
247
|
+
return findings;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
export default LLMRedTeam;
|