@datahogo/core 0.1.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/LICENSE +661 -0
- package/README.md +17 -0
- package/dist/engines/config.d.ts +3 -0
- package/dist/engines/config.d.ts.map +1 -0
- package/dist/engines/config.js +433 -0
- package/dist/engines/config.js.map +1 -0
- package/dist/engines/dast.d.ts +3 -0
- package/dist/engines/dast.d.ts.map +1 -0
- package/dist/engines/dast.js +167 -0
- package/dist/engines/dast.js.map +1 -0
- package/dist/engines/db-rules.d.ts +3 -0
- package/dist/engines/db-rules.d.ts.map +1 -0
- package/dist/engines/db-rules.js +166 -0
- package/dist/engines/db-rules.js.map +1 -0
- package/dist/engines/dependencies.d.ts +3 -0
- package/dist/engines/dependencies.d.ts.map +1 -0
- package/dist/engines/dependencies.js +167 -0
- package/dist/engines/dependencies.js.map +1 -0
- package/dist/engines/github-actions.d.ts +3 -0
- package/dist/engines/github-actions.d.ts.map +1 -0
- package/dist/engines/github-actions.js +215 -0
- package/dist/engines/github-actions.js.map +1 -0
- package/dist/engines/gitleaks.d.ts +3 -0
- package/dist/engines/gitleaks.d.ts.map +1 -0
- package/dist/engines/gitleaks.js +107 -0
- package/dist/engines/gitleaks.js.map +1 -0
- package/dist/engines/npm-audit.d.ts +3 -0
- package/dist/engines/npm-audit.d.ts.map +1 -0
- package/dist/engines/npm-audit.js +113 -0
- package/dist/engines/npm-audit.js.map +1 -0
- package/dist/engines/patterns.d.ts +3 -0
- package/dist/engines/patterns.d.ts.map +1 -0
- package/dist/engines/patterns.js +1330 -0
- package/dist/engines/patterns.js.map +1 -0
- package/dist/engines/secrets.d.ts +4 -0
- package/dist/engines/secrets.d.ts.map +1 -0
- package/dist/engines/secrets.js +260 -0
- package/dist/engines/secrets.js.map +1 -0
- package/dist/engines/semgrep.d.ts +3 -0
- package/dist/engines/semgrep.d.ts.map +1 -0
- package/dist/engines/semgrep.js +173 -0
- package/dist/engines/semgrep.js.map +1 -0
- package/dist/engines/types.d.ts +30 -0
- package/dist/engines/types.d.ts.map +1 -0
- package/dist/engines/types.js +3 -0
- package/dist/engines/types.js.map +1 -0
- package/dist/engines/url-scanner.d.ts +3 -0
- package/dist/engines/url-scanner.d.ts.map +1 -0
- package/dist/engines/url-scanner.js +469 -0
- package/dist/engines/url-scanner.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/orchestrator.d.ts +32 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +237 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/rules/.gitkeep +0 -0
- package/dist/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/api-security.yaml +489 -0
- package/dist/rules/auth-patterns.yaml +406 -0
- package/dist/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/general-security.yaml +588 -0
- package/dist/rules/injection-patterns.yaml +318 -0
- package/dist/rules/nextjs-security.yaml +532 -0
- package/dist/rules/node-security.yaml +380 -0
- package/dist/rules/rules/.gitkeep +0 -0
- package/dist/rules/rules/ai-llm-security.yaml +199 -0
- package/dist/rules/rules/api-security.yaml +489 -0
- package/dist/rules/rules/auth-patterns.yaml +406 -0
- package/dist/rules/rules/crypto-patterns.yaml +227 -0
- package/dist/rules/rules/database-cloud-security.yaml +169 -0
- package/dist/rules/rules/general-security.yaml +588 -0
- package/dist/rules/rules/injection-patterns.yaml +318 -0
- package/dist/rules/rules/nextjs-security.yaml +532 -0
- package/dist/rules/rules/node-security.yaml +380 -0
- package/dist/rules/rules/supabase-security.yaml +387 -0
- package/dist/rules/supabase-security.yaml +387 -0
- package/dist/scanning/agents/csharp-agent.d.ts +12 -0
- package/dist/scanning/agents/csharp-agent.d.ts.map +1 -0
- package/dist/scanning/agents/csharp-agent.js +592 -0
- package/dist/scanning/agents/csharp-agent.js.map +1 -0
- package/dist/scanning/agents/go-agent.d.ts +14 -0
- package/dist/scanning/agents/go-agent.d.ts.map +1 -0
- package/dist/scanning/agents/go-agent.js +641 -0
- package/dist/scanning/agents/go-agent.js.map +1 -0
- package/dist/scanning/agents/java-agent.d.ts +8 -0
- package/dist/scanning/agents/java-agent.d.ts.map +1 -0
- package/dist/scanning/agents/java-agent.js +807 -0
- package/dist/scanning/agents/java-agent.js.map +1 -0
- package/dist/scanning/agents/javascript-agent.d.ts +8 -0
- package/dist/scanning/agents/javascript-agent.d.ts.map +1 -0
- package/dist/scanning/agents/javascript-agent.js +77 -0
- package/dist/scanning/agents/javascript-agent.js.map +1 -0
- package/dist/scanning/agents/mobile-agent.d.ts +8 -0
- package/dist/scanning/agents/mobile-agent.d.ts.map +1 -0
- package/dist/scanning/agents/mobile-agent.js +916 -0
- package/dist/scanning/agents/mobile-agent.js.map +1 -0
- package/dist/scanning/agents/php-agent.d.ts +8 -0
- package/dist/scanning/agents/php-agent.d.ts.map +1 -0
- package/dist/scanning/agents/php-agent.js +679 -0
- package/dist/scanning/agents/php-agent.js.map +1 -0
- package/dist/scanning/agents/python-agent.d.ts +8 -0
- package/dist/scanning/agents/python-agent.d.ts.map +1 -0
- package/dist/scanning/agents/python-agent.js +701 -0
- package/dist/scanning/agents/python-agent.js.map +1 -0
- package/dist/scanning/agents/supabase-agent.d.ts +8 -0
- package/dist/scanning/agents/supabase-agent.d.ts.map +1 -0
- package/dist/scanning/agents/supabase-agent.js +484 -0
- package/dist/scanning/agents/supabase-agent.js.map +1 -0
- package/dist/scanning/orchestrator.d.ts +29 -0
- package/dist/scanning/orchestrator.d.ts.map +1 -0
- package/dist/scanning/orchestrator.js +186 -0
- package/dist/scanning/orchestrator.js.map +1 -0
- package/dist/scanning/tech-detector.d.ts +12 -0
- package/dist/scanning/tech-detector.d.ts.map +1 -0
- package/dist/scanning/tech-detector.js +252 -0
- package/dist/scanning/tech-detector.js.map +1 -0
- package/dist/scanning/types.d.ts +87 -0
- package/dist/scanning/types.d.ts.map +1 -0
- package/dist/scanning/types.js +5 -0
- package/dist/scanning/types.js.map +1 -0
- package/dist/utils/child-process.d.ts +2 -0
- package/dist/utils/child-process.d.ts.map +1 -0
- package/dist/utils/child-process.js +4 -0
- package/dist/utils/child-process.js.map +1 -0
- package/dist/utils/context-classifier.d.ts +11 -0
- package/dist/utils/context-classifier.d.ts.map +1 -0
- package/dist/utils/context-classifier.js +79 -0
- package/dist/utils/context-classifier.js.map +1 -0
- package/dist/utils/exec.d.ts +18 -0
- package/dist/utils/exec.d.ts.map +1 -0
- package/dist/utils/exec.js +51 -0
- package/dist/utils/exec.js.map +1 -0
- package/dist/utils/file-filter.d.ts +4 -0
- package/dist/utils/file-filter.d.ts.map +1 -0
- package/dist/utils/file-filter.js +68 -0
- package/dist/utils/file-filter.js.map +1 -0
- package/dist/utils/fs.d.ts +2 -0
- package/dist/utils/fs.d.ts.map +1 -0
- package/dist/utils/fs.js +5 -0
- package/dist/utils/fs.js.map +1 -0
- package/dist/utils/logger.d.ts +12 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +27 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/post-processor.d.ts +10 -0
- package/dist/utils/post-processor.d.ts.map +1 -0
- package/dist/utils/post-processor.js +183 -0
- package/dist/utils/post-processor.js.map +1 -0
- package/package.json +44 -0
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
// Code pattern analysis engine - detects insecure code patterns
|
|
2
|
+
// In production, this would call Semgrep. For now, uses regex-based detection.
|
|
3
|
+
const PATTERN_RULES = [
|
|
4
|
+
// === Broken Access Control ===
|
|
5
|
+
{
|
|
6
|
+
id: 1,
|
|
7
|
+
name: "API Route Without Auth Check",
|
|
8
|
+
severity: "high",
|
|
9
|
+
category: "web-owasp",
|
|
10
|
+
pattern: /export\s+async\s+function\s+(?:GET|POST|PUT|PATCH|DELETE)\s*\([^)]*\)\s*\{(?:(?!auth\.getUser|getUser|getSession|verifyAuth|requireAuth|Bearer|authorization|CRON_SECRET|WORKER_API_KEY|verifySignature)[\s\S]){0,500}(?:Response\.json|NextResponse)/,
|
|
11
|
+
fileFilter: /\/api\/(?!webhook|auth)/,
|
|
12
|
+
owasp: "A01:2021",
|
|
13
|
+
},
|
|
14
|
+
// === Injection ===
|
|
15
|
+
{
|
|
16
|
+
id: 5,
|
|
17
|
+
name: "Potential SQL Injection",
|
|
18
|
+
severity: "critical",
|
|
19
|
+
category: "web-owasp",
|
|
20
|
+
pattern: /(?:\$queryRaw|\.query|sequelize\.query|execute)\s*\(\s*`[^`]*\$\{/,
|
|
21
|
+
owasp: "A03:2021",
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
id: 61,
|
|
25
|
+
name: "eval() with Dynamic Input",
|
|
26
|
+
severity: "critical",
|
|
27
|
+
category: "vibecoding",
|
|
28
|
+
pattern: /(?:eval|Function)\s*\(\s*(?:[^"'`)\s]|`[^`]*\$\{)/,
|
|
29
|
+
owasp: "A03:2021",
|
|
30
|
+
},
|
|
31
|
+
// === Cryptographic Failures ===
|
|
32
|
+
{
|
|
33
|
+
id: 2,
|
|
34
|
+
name: "Weak Hash Algorithm (MD5/SHA1)",
|
|
35
|
+
severity: "high",
|
|
36
|
+
category: "web-owasp",
|
|
37
|
+
pattern: /createHash\s*\(\s*["'](?:md5|sha1)["']\s*\)/,
|
|
38
|
+
owasp: "A02:2021",
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
id: 125,
|
|
42
|
+
name: "Math.random for Security Purpose",
|
|
43
|
+
severity: "high",
|
|
44
|
+
category: "cryptography",
|
|
45
|
+
pattern: /(?:token|secret|key|session|id|nonce|salt)[\s\S]{0,30}Math\.random/i,
|
|
46
|
+
owasp: "A02:2021",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
id: 126,
|
|
50
|
+
name: "JWT Algorithm None Allowed",
|
|
51
|
+
severity: "critical",
|
|
52
|
+
category: "cryptography",
|
|
53
|
+
pattern: /algorithms\s*:\s*\[.*["']none["']/i,
|
|
54
|
+
},
|
|
55
|
+
// === XSS ===
|
|
56
|
+
// dangerouslySetInnerHTML is handled as a special case in analyzePatterns()
|
|
57
|
+
// because we need file-level context (imports, variable sources) to reduce false positives.
|
|
58
|
+
{
|
|
59
|
+
id: 86,
|
|
60
|
+
name: "DOM-based XSS Risk",
|
|
61
|
+
severity: "high",
|
|
62
|
+
category: "frontend",
|
|
63
|
+
pattern: /(?:document\.write|\.innerHTML\s*=)\s*(?:.*(?:location|document\.URL|window\.name|document\.referrer))/,
|
|
64
|
+
owasp: "A03:2021",
|
|
65
|
+
},
|
|
66
|
+
// === Token Storage ===
|
|
67
|
+
{
|
|
68
|
+
id: 47,
|
|
69
|
+
name: "Tokens Stored in localStorage",
|
|
70
|
+
severity: "high",
|
|
71
|
+
category: "react-nextjs",
|
|
72
|
+
pattern: /localStorage\.setItem\s*\(\s*["'](?:token|jwt|auth|session|access_token|refresh_token)["']/i,
|
|
73
|
+
owasp: "A07:2021",
|
|
74
|
+
},
|
|
75
|
+
// === Security Misconfiguration ===
|
|
76
|
+
{
|
|
77
|
+
id: 4,
|
|
78
|
+
name: "Open CORS Policy",
|
|
79
|
+
severity: "medium",
|
|
80
|
+
category: "web-owasp",
|
|
81
|
+
pattern: /Access-Control-Allow-Origin['":\s]*['"]\*['"]/,
|
|
82
|
+
owasp: "A05:2021",
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 50,
|
|
86
|
+
name: "CORS Wildcard in API Route",
|
|
87
|
+
severity: "medium",
|
|
88
|
+
category: "react-nextjs",
|
|
89
|
+
pattern: /(?:headers\.set|res\.setHeader)\s*\(\s*["']Access-Control-Allow-Origin["']\s*,\s*["']\*["']\)/,
|
|
90
|
+
fileFilter: /\/api\//,
|
|
91
|
+
},
|
|
92
|
+
// === Supabase Specific ===
|
|
93
|
+
{
|
|
94
|
+
id: 34,
|
|
95
|
+
name: "Supabase Service Role Key in Client Code",
|
|
96
|
+
severity: "critical",
|
|
97
|
+
category: "supabase",
|
|
98
|
+
pattern: /NEXT_PUBLIC_.*SERVICE_ROLE|["']use client["'][\s\S]*service_role/i,
|
|
99
|
+
owasp: "A07:2021",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 31,
|
|
103
|
+
name: "RLS Disabled on Table",
|
|
104
|
+
severity: "critical",
|
|
105
|
+
category: "supabase",
|
|
106
|
+
// Fixed: \Z is not valid JS regex — use $ (matches end of string without /m flag).
|
|
107
|
+
// This checks that between this CREATE TABLE and the next (or end of file),
|
|
108
|
+
// there is no ENABLE ROW LEVEL SECURITY statement.
|
|
109
|
+
pattern: /CREATE\s+TABLE(?:(?!ENABLE\s+ROW\s+LEVEL\s+SECURITY)[\s\S])*?(?=CREATE\s+TABLE|$)/i,
|
|
110
|
+
fileFilter: /\.sql$/,
|
|
111
|
+
confidence: "medium",
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
id: 32,
|
|
115
|
+
name: "RLS Policy with USING(true)",
|
|
116
|
+
severity: "critical",
|
|
117
|
+
category: "supabase",
|
|
118
|
+
pattern: /CREATE\s+POLICY[\s\S]*?USING\s*\(\s*true\s*\)/i,
|
|
119
|
+
fileFilter: /\.sql$/,
|
|
120
|
+
},
|
|
121
|
+
// === Input Validation ===
|
|
122
|
+
{
|
|
123
|
+
id: 54,
|
|
124
|
+
name: "No Input Validation on API Route",
|
|
125
|
+
severity: "medium",
|
|
126
|
+
category: "vibecoding",
|
|
127
|
+
pattern: /(?:req\.body|request\.json\(\))[\s\S]{0,100}(?:\.insert|\.update|\.create)(?![\s\S]{0,200}(?:safeParse|parse|validate|schema|zod))/,
|
|
128
|
+
fileFilter: /\/api\//,
|
|
129
|
+
},
|
|
130
|
+
// === JWT Issues ===
|
|
131
|
+
{
|
|
132
|
+
id: 56,
|
|
133
|
+
name: "JWT Without Expiration",
|
|
134
|
+
severity: "high",
|
|
135
|
+
category: "vibecoding",
|
|
136
|
+
pattern: /jwt\.sign\s*\([^)]*(?!\bexpiresIn\b|\bexp\b)[^)]*\)/,
|
|
137
|
+
},
|
|
138
|
+
// === Verbose Errors ===
|
|
139
|
+
{
|
|
140
|
+
id: 59,
|
|
141
|
+
name: "Verbose Error Response",
|
|
142
|
+
severity: "medium",
|
|
143
|
+
category: "vibecoding",
|
|
144
|
+
// Only flag when error object properties (.stack, .message) are directly
|
|
145
|
+
// inside the JSON response body — not generic string messages like { error: "Unauthorized" }.
|
|
146
|
+
// [^}]* prevents matching across multiple object literals (no greedy [\s\S]).
|
|
147
|
+
pattern: /Response\.json\s*\(\s*\{[^}]*(?:\.stack|\.trace|(?:err|error|e)\.message)[^}]*\}\s*,\s*\{\s*status:\s*5/,
|
|
148
|
+
fileFilter: /\/api\//,
|
|
149
|
+
confidence: "medium",
|
|
150
|
+
},
|
|
151
|
+
{
|
|
152
|
+
id: 134,
|
|
153
|
+
name: "Environment Variables Logged",
|
|
154
|
+
severity: "high",
|
|
155
|
+
category: "serverless",
|
|
156
|
+
pattern: /console\.log\s*\(\s*process\.env/,
|
|
157
|
+
},
|
|
158
|
+
// === Auth Patterns ===
|
|
159
|
+
{
|
|
160
|
+
id: 57,
|
|
161
|
+
name: "Auth Logic in Frontend Only",
|
|
162
|
+
severity: "high",
|
|
163
|
+
category: "vibecoding",
|
|
164
|
+
pattern: /["']use client["'][\s\S]*?(?:isAdmin|isAuth|role\s*===|user\.role)/,
|
|
165
|
+
fileFilter: /\.(?:tsx|jsx)$/,
|
|
166
|
+
},
|
|
167
|
+
// === Open Redirect ===
|
|
168
|
+
{
|
|
169
|
+
id: 119,
|
|
170
|
+
name: "Open Redirect",
|
|
171
|
+
severity: "medium",
|
|
172
|
+
category: "logic",
|
|
173
|
+
pattern: /(?:redirect|location\.href|window\.location)\s*[=(]\s*(?:req\.query|searchParams\.get|params)/,
|
|
174
|
+
owasp: "A01:2021",
|
|
175
|
+
},
|
|
176
|
+
// === Prototype Pollution ===
|
|
177
|
+
{
|
|
178
|
+
id: 128,
|
|
179
|
+
name: "Potential Prototype Pollution",
|
|
180
|
+
severity: "high",
|
|
181
|
+
category: "javascript",
|
|
182
|
+
pattern: /(?:Object\.assign|\.\.\.(?:req\.body|body|params|query))[\s\S]{0,50}(?:\.update|\.create|\.insert)/,
|
|
183
|
+
},
|
|
184
|
+
// === Certificate Validation ===
|
|
185
|
+
{
|
|
186
|
+
id: 124,
|
|
187
|
+
name: "TLS Certificate Validation Disabled",
|
|
188
|
+
severity: "critical",
|
|
189
|
+
category: "cryptography",
|
|
190
|
+
pattern: /NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*["']?0/,
|
|
191
|
+
},
|
|
192
|
+
// === Debug/Test Routes ===
|
|
193
|
+
{
|
|
194
|
+
id: 116,
|
|
195
|
+
name: "Debug Route in Production",
|
|
196
|
+
severity: "medium",
|
|
197
|
+
category: "logic",
|
|
198
|
+
pattern: /(?:\/debug|\/test|\/seed|\/admin-test)[\s'"]/,
|
|
199
|
+
fileFilter: /(?:route|router|app)\./,
|
|
200
|
+
},
|
|
201
|
+
// === File/Path Issues ===
|
|
202
|
+
{
|
|
203
|
+
id: 106,
|
|
204
|
+
name: "Path Traversal Risk",
|
|
205
|
+
severity: "high",
|
|
206
|
+
category: "files",
|
|
207
|
+
pattern: /(?:readFile|createReadStream|readdir|unlink|writeFile)\s*\([^)]*(?:req\.|params\.|query\.)/,
|
|
208
|
+
owasp: "A01:2021",
|
|
209
|
+
},
|
|
210
|
+
// === WebSocket ===
|
|
211
|
+
{
|
|
212
|
+
id: 199,
|
|
213
|
+
name: "WebSocket Without Authentication",
|
|
214
|
+
severity: "high",
|
|
215
|
+
category: "api",
|
|
216
|
+
pattern: /(?:new\s+WebSocket|ws\.on\s*\(\s*["']connection["'])(?:(?!auth|token|verify|session)[\s\S]){0,300}/,
|
|
217
|
+
},
|
|
218
|
+
// =============================================
|
|
219
|
+
// EXPANDED PATTERNS — Injections (IDs 72-76)
|
|
220
|
+
// =============================================
|
|
221
|
+
{
|
|
222
|
+
id: 72,
|
|
223
|
+
name: "XML External Entity (XXE) Risk",
|
|
224
|
+
severity: "high",
|
|
225
|
+
category: "injection",
|
|
226
|
+
pattern: /(?:DOMParser|xml2js\.parse|parseString|libxmljs\.parseXml|fast-xml-parser)(?:(?!disableEntities|noent.*false|DISALLOW_DOCTYPE)[\s\S]){0,200}/,
|
|
227
|
+
owasp: "A03:2021",
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
id: 73,
|
|
231
|
+
name: "HTTP Header Injection (CRLF)",
|
|
232
|
+
severity: "medium",
|
|
233
|
+
category: "injection",
|
|
234
|
+
pattern: /(?:res\.setHeader|response\.headers\.set|res\.writeHead)\s*\([^,]*,\s*(?:req\.|params\.|query\.|searchParams\.get)/,
|
|
235
|
+
owasp: "A03:2021",
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
id: 74,
|
|
239
|
+
name: "Log Injection — Unsanitized User Input in Logs",
|
|
240
|
+
severity: "low",
|
|
241
|
+
category: "injection",
|
|
242
|
+
pattern: /(?:console\.(?:log|info|warn|error)|logger\.(?:info|warn|error|debug))\s*\([^)]*(?:req\.body|req\.query|req\.params|searchParams\.get)/,
|
|
243
|
+
owasp: "A09:2021",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: 75,
|
|
247
|
+
name: "Command Injection Risk",
|
|
248
|
+
severity: "critical",
|
|
249
|
+
category: "injection",
|
|
250
|
+
pattern: /(?:child_process\.exec|execSync|exec)\s*\(\s*(?:`[^`]*\$\{|[^"'`\s]+\s*\+\s*(?:req\.|params\.|query\.|body\.))/,
|
|
251
|
+
owasp: "A03:2021",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: 76,
|
|
255
|
+
name: "Server-Side Template Injection (SSTI)",
|
|
256
|
+
severity: "critical",
|
|
257
|
+
category: "injection",
|
|
258
|
+
pattern: /(?:ejs\.render|pug\.render|Handlebars\.compile|nunjucks\.renderString|mustache\.render)\s*\([^)]*(?:req\.|params\.|body\.|query\.)/,
|
|
259
|
+
owasp: "A03:2021",
|
|
260
|
+
},
|
|
261
|
+
// =============================================
|
|
262
|
+
// Auth Expanded (IDs 77-85)
|
|
263
|
+
// =============================================
|
|
264
|
+
{
|
|
265
|
+
id: 78,
|
|
266
|
+
name: "Authentication Endpoint Without Rate Limiting",
|
|
267
|
+
severity: "medium",
|
|
268
|
+
category: "auth",
|
|
269
|
+
pattern: /(?:\/login|\/signin|\/auth|\/register|\/signup|\/reset-password)[\s\S]{0,500}(?:export\s+async\s+function\s+POST)(?:(?!rateLimit|rateLimiter|throttle|limiter)[\s\S]){0,500}/,
|
|
270
|
+
fileFilter: /\/api\//,
|
|
271
|
+
owasp: "A07:2021",
|
|
272
|
+
confidence: "low",
|
|
273
|
+
suppressedByMiddleware: true,
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
id: 79,
|
|
277
|
+
name: "Password Reset Token in URL",
|
|
278
|
+
severity: "medium",
|
|
279
|
+
category: "auth",
|
|
280
|
+
pattern: /(?:reset|forgot|recover).*(?:token|code)[\s\S]{0,100}(?:searchParams|query\.|req\.query)/i,
|
|
281
|
+
owasp: "A07:2021",
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
id: 82,
|
|
285
|
+
name: "User Enumeration via Error Message",
|
|
286
|
+
severity: "low",
|
|
287
|
+
category: "auth",
|
|
288
|
+
pattern: /(?:["'](?:user\s+not\s+found|email\s+not\s+registered|no\s+account\s+found|invalid\s+username|username\s+does\s+not\s+exist)["'])/i,
|
|
289
|
+
fileFilter: /\/api\//,
|
|
290
|
+
owasp: "A07:2021",
|
|
291
|
+
},
|
|
292
|
+
{
|
|
293
|
+
id: 85,
|
|
294
|
+
name: "OAuth Missing State Parameter",
|
|
295
|
+
severity: "high",
|
|
296
|
+
category: "auth",
|
|
297
|
+
pattern: /(?:authorize\?|oauth.*\?)(?:(?!state=)[\s\S]){0,200}(?:client_id|redirect_uri)/i,
|
|
298
|
+
owasp: "A07:2021",
|
|
299
|
+
},
|
|
300
|
+
// =============================================
|
|
301
|
+
// Frontend Expanded (IDs 86-93)
|
|
302
|
+
// =============================================
|
|
303
|
+
{
|
|
304
|
+
id: 88,
|
|
305
|
+
name: "PostMessage Without Origin Verification",
|
|
306
|
+
severity: "medium",
|
|
307
|
+
category: "frontend",
|
|
308
|
+
pattern: /addEventListener\s*\(\s*["']message["']\s*,\s*(?:function|\()\s*\w*\s*[){](?:(?!\.origin|event\.source)[\s\S]){0,300}/,
|
|
309
|
+
fileFilter: /\.(?:tsx|jsx|ts|js)$/,
|
|
310
|
+
owasp: "A01:2021",
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
id: 89,
|
|
314
|
+
name: "Insecure CDN Link (HTTP)",
|
|
315
|
+
severity: "medium",
|
|
316
|
+
category: "frontend",
|
|
317
|
+
pattern: /(?:src|href)\s*=\s*["']http:\/\/(?:cdn|cdnjs|unpkg|jsdelivr|ajax\.googleapis)/,
|
|
318
|
+
fileFilter: /\.(?:tsx|jsx|html)$/,
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
id: 91,
|
|
322
|
+
name: "Potential Regular Expression DoS (ReDoS)",
|
|
323
|
+
severity: "medium",
|
|
324
|
+
category: "frontend",
|
|
325
|
+
pattern: /new\s+RegExp\s*\(\s*(?:[^"'`)\s]|`[^`]*\$\{)/,
|
|
326
|
+
// Only flag in API routes and request handlers where user input could reach the regex.
|
|
327
|
+
// Internal tools and analyzers use controlled/extracted data, not raw user input.
|
|
328
|
+
fileFilter: /(?:\/api\/|\/server|\.server\.|route\.|controller|handler|middleware)/i,
|
|
329
|
+
owasp: "A03:2021",
|
|
330
|
+
},
|
|
331
|
+
{
|
|
332
|
+
id: 92,
|
|
333
|
+
name: "Insecure Iframe — Missing Sandbox",
|
|
334
|
+
severity: "medium",
|
|
335
|
+
category: "frontend",
|
|
336
|
+
pattern: /<iframe(?:(?!sandbox)[\s\S]){0,200}(?:src\s*=)/i,
|
|
337
|
+
fileFilter: /\.(?:tsx|jsx|html)$/,
|
|
338
|
+
},
|
|
339
|
+
// =============================================
|
|
340
|
+
// APIs Expanded (IDs 94-100)
|
|
341
|
+
// =============================================
|
|
342
|
+
{
|
|
343
|
+
id: 96,
|
|
344
|
+
name: "API Route Without Rate Limiting",
|
|
345
|
+
severity: "medium",
|
|
346
|
+
category: "api",
|
|
347
|
+
pattern: /export\s+async\s+function\s+(?:GET|POST|PUT|PATCH|DELETE)(?:(?!rateLimit|rateLimiter|throttle|limiter)[\s\S]){20,1000}/,
|
|
348
|
+
fileFilter: /\/api\/(?!webhook)/,
|
|
349
|
+
owasp: "A04:2023",
|
|
350
|
+
confidence: "low",
|
|
351
|
+
suppressedByMiddleware: true,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
id: 98,
|
|
355
|
+
name: "Mass Assignment — Request Body Directly to Database",
|
|
356
|
+
severity: "high",
|
|
357
|
+
category: "api",
|
|
358
|
+
pattern: /(?:\.update|\.create|\.insert|\.upsert)\s*\(\s*(?:req\.body|body|await\s+request\.json\(\))\s*\)/,
|
|
359
|
+
fileFilter: /\/api\//,
|
|
360
|
+
owasp: "A01:2021",
|
|
361
|
+
},
|
|
362
|
+
{
|
|
363
|
+
id: 99,
|
|
364
|
+
name: "Excessive Data Exposure — SELECT *",
|
|
365
|
+
severity: "medium",
|
|
366
|
+
category: "api",
|
|
367
|
+
pattern: /\.select\s*\(\s*["']\*["']\s*\)/,
|
|
368
|
+
fileFilter: /\/api\//,
|
|
369
|
+
owasp: "A03:2023",
|
|
370
|
+
},
|
|
371
|
+
// =============================================
|
|
372
|
+
// Database General (IDs 101-105)
|
|
373
|
+
// =============================================
|
|
374
|
+
{
|
|
375
|
+
id: 102,
|
|
376
|
+
name: "NoSQL Injection Risk",
|
|
377
|
+
severity: "high",
|
|
378
|
+
category: "database",
|
|
379
|
+
pattern: /\.find\s*\(\s*(?:req\.body|req\.query|body|JSON\.parse)/,
|
|
380
|
+
owasp: "A03:2021",
|
|
381
|
+
},
|
|
382
|
+
{
|
|
383
|
+
id: 103,
|
|
384
|
+
name: "ORM Injection — Raw Query with User Input",
|
|
385
|
+
severity: "high",
|
|
386
|
+
category: "database",
|
|
387
|
+
pattern: /(?:\$queryRaw|\.raw|Sequelize\.literal|knex\.raw)\s*\(\s*(?:`[^`]*\$\{|[^"'`]+\+)/,
|
|
388
|
+
// Only flag in server/API files — marketing pages and components use code examples
|
|
389
|
+
// that contain raw query patterns as educational content (JSX string literals)
|
|
390
|
+
fileFilter: /(?:\/api\/|\/lib\/|\/server|\.server\.|route\.|controller|handler|model|service)/i,
|
|
391
|
+
owasp: "A03:2021",
|
|
392
|
+
},
|
|
393
|
+
// =============================================
|
|
394
|
+
// Files & Media (IDs 106-112)
|
|
395
|
+
// =============================================
|
|
396
|
+
{
|
|
397
|
+
id: 107,
|
|
398
|
+
name: "File Upload Without Type Validation",
|
|
399
|
+
severity: "high",
|
|
400
|
+
category: "files",
|
|
401
|
+
pattern: /(?:multer|formidable|busboy|multiparty)(?:(?!fileFilter|mimetype|allowedTypes|accept)[\s\S]){0,500}(?:upload|file|single|array|fields)/i,
|
|
402
|
+
owasp: "A04:2021",
|
|
403
|
+
},
|
|
404
|
+
{
|
|
405
|
+
id: 108,
|
|
406
|
+
name: "File Upload Without Size Limit",
|
|
407
|
+
severity: "medium",
|
|
408
|
+
category: "files",
|
|
409
|
+
pattern: /(?:multer|formidable|busboy)(?:(?!limits|maxFileSize|maxSize|fileSizeLimit)[\s\S]){0,500}(?:upload|single|array)/i,
|
|
410
|
+
owasp: "A04:2021",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: 110,
|
|
414
|
+
name: "Insecure Temp File Creation",
|
|
415
|
+
severity: "medium",
|
|
416
|
+
category: "files",
|
|
417
|
+
pattern: /(?:writeFileSync|createWriteStream)\s*\(\s*(?:["']\/tmp\/|os\.tmpdir\(\))/,
|
|
418
|
+
},
|
|
419
|
+
// =============================================
|
|
420
|
+
// Race Conditions & Logic (IDs 113-120)
|
|
421
|
+
// =============================================
|
|
422
|
+
{
|
|
423
|
+
id: 114,
|
|
424
|
+
name: "Price or Amount Accepted from Client Input",
|
|
425
|
+
severity: "critical",
|
|
426
|
+
category: "logic",
|
|
427
|
+
pattern: /(?:price|amount|total|cost|quantity)\s*[=:]\s*(?:req\.body|body|params|query)\./i,
|
|
428
|
+
fileFilter: /\/api\//,
|
|
429
|
+
owasp: "A04:2021",
|
|
430
|
+
},
|
|
431
|
+
{
|
|
432
|
+
id: 115,
|
|
433
|
+
name: "TOCTOU — Time of Check to Time of Use",
|
|
434
|
+
severity: "medium",
|
|
435
|
+
category: "logic",
|
|
436
|
+
pattern: /(?:existsSync|accessSync|statSync)\s*\([^)]+\)[\s\S]{0,100}(?:readFileSync|unlinkSync|writeFileSync)/,
|
|
437
|
+
// Only flag in API routes and server handlers where concurrent requests matter.
|
|
438
|
+
// Build-time utilities (content loaders, scripts) are single-threaded and safe.
|
|
439
|
+
fileFilter: /(?:\/api\/|\/server|\.server\.|route\.|controller|handler|middleware)/i,
|
|
440
|
+
},
|
|
441
|
+
// =============================================
|
|
442
|
+
// JavaScript/Node (IDs 128-131)
|
|
443
|
+
// =============================================
|
|
444
|
+
{
|
|
445
|
+
id: 129,
|
|
446
|
+
name: "Unsafe Deserialization",
|
|
447
|
+
severity: "critical",
|
|
448
|
+
category: "javascript",
|
|
449
|
+
pattern: /(?:serialize|unserialize|deserialize|node-serialize|serialize-javascript)\s*\(\s*(?:req\.|body|params|query|JSON\.parse)/i,
|
|
450
|
+
owasp: "A08:2021",
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: 130,
|
|
454
|
+
name: "Buffer Allocation Without Fill",
|
|
455
|
+
severity: "medium",
|
|
456
|
+
category: "javascript",
|
|
457
|
+
pattern: /Buffer\.allocUnsafe\s*\(|new\s+Buffer\s*\(\s*\d/,
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: 131,
|
|
461
|
+
name: "Synchronous Operation Blocking Event Loop",
|
|
462
|
+
severity: "low",
|
|
463
|
+
category: "javascript",
|
|
464
|
+
pattern: /(?:readFileSync|writeFileSync|execSync|spawnSync|accessSync)\s*\(/,
|
|
465
|
+
fileFilter: /\/api\//,
|
|
466
|
+
},
|
|
467
|
+
// =============================================
|
|
468
|
+
// Serverless & Cloud (IDs 132-136)
|
|
469
|
+
// =============================================
|
|
470
|
+
{
|
|
471
|
+
id: 133,
|
|
472
|
+
name: "Function with Overly Broad Permissions",
|
|
473
|
+
severity: "medium",
|
|
474
|
+
category: "serverless",
|
|
475
|
+
pattern: /(?:policy|permissions|role)[\s\S]{0,100}(?:["']\*["']|AdministratorAccess|FullAccess)/i,
|
|
476
|
+
fileFilter: /(?:serverless|sam|template|cdk|terraform)/,
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: 135,
|
|
480
|
+
name: "Verbose Error in Production Response",
|
|
481
|
+
severity: "medium",
|
|
482
|
+
category: "serverless",
|
|
483
|
+
// Only flag when error object properties are interpolated INTO the response body.
|
|
484
|
+
// Safe pattern: console.error(e.message) then return { error: "generic string" }
|
|
485
|
+
// Unsafe pattern: return Response.json({ error: e.message }) — exposes internals.
|
|
486
|
+
// Increase lookahead distance but skip blocks that log to console first (safe pattern).
|
|
487
|
+
pattern: /catch\s*\(\s*(\w+)\s*\)\s*\{(?:(?!console\.(?:error|warn|log))[\s\S]){0,80}(?:Response\.json|res\.(?:json|send))\s*\(\s*\{[^}]*(?:\1\.stack|\1\.message|\1\.toString\(\))/,
|
|
488
|
+
fileFilter: /\/api\//,
|
|
489
|
+
confidence: "medium",
|
|
490
|
+
},
|
|
491
|
+
// =============================================
|
|
492
|
+
// Supply Chain (IDs 137-141)
|
|
493
|
+
// =============================================
|
|
494
|
+
{
|
|
495
|
+
id: 138,
|
|
496
|
+
name: "Suspicious Postinstall Script",
|
|
497
|
+
severity: "high",
|
|
498
|
+
category: "supply-chain",
|
|
499
|
+
pattern: /["'](?:postinstall|preinstall|install)["']\s*:\s*["'][^"']*(?:curl|wget|bash|sh\s|node\s+-e|eval)/,
|
|
500
|
+
fileFilter: /package\.json$/,
|
|
501
|
+
},
|
|
502
|
+
// =============================================
|
|
503
|
+
// Crypto Expanded (IDs 121-127)
|
|
504
|
+
// =============================================
|
|
505
|
+
{
|
|
506
|
+
id: 121,
|
|
507
|
+
name: "Hardcoded Encryption Key",
|
|
508
|
+
severity: "critical",
|
|
509
|
+
category: "cryptography",
|
|
510
|
+
pattern: /(?:createCipher|createCipheriv|createDecipher|createDecipheriv)\s*\([^)]*["'][^"']{8,}["']/,
|
|
511
|
+
owasp: "A02:2021",
|
|
512
|
+
},
|
|
513
|
+
{
|
|
514
|
+
id: 122,
|
|
515
|
+
name: "ECB Mode Used for Encryption",
|
|
516
|
+
severity: "high",
|
|
517
|
+
category: "cryptography",
|
|
518
|
+
pattern: /(?:aes|des|blowfish|rc4).*ecb/i,
|
|
519
|
+
owasp: "A02:2021",
|
|
520
|
+
},
|
|
521
|
+
{
|
|
522
|
+
id: 123,
|
|
523
|
+
name: "Static IV for Encryption",
|
|
524
|
+
severity: "high",
|
|
525
|
+
category: "cryptography",
|
|
526
|
+
pattern: /(?:iv|nonce|initVector)\s*=\s*(?:Buffer\.from\s*\(\s*["']|["'][0-9a-f]{16,}["'])/i,
|
|
527
|
+
owasp: "A02:2021",
|
|
528
|
+
},
|
|
529
|
+
// =============================================
|
|
530
|
+
// Framework-agnostic patterns (Express/Fastify/Hono/Koa)
|
|
531
|
+
// These detect issues in ANY Node.js backend, not just Next.js
|
|
532
|
+
// =============================================
|
|
533
|
+
// Express/Fastify route without auth middleware
|
|
534
|
+
// In Fastify: fastify.post('/path', handler) = no auth (bad)
|
|
535
|
+
// fastify.post('/path', { preHandler }, handler) = has auth (good)
|
|
536
|
+
// In Express: app.post('/path', handler) = no auth (bad)
|
|
537
|
+
// app.post('/path', authMiddleware, handler) = has auth (good)
|
|
538
|
+
// We flag write routes (POST/PUT/PATCH/DELETE) that go directly to a handler function
|
|
539
|
+
{
|
|
540
|
+
id: 1,
|
|
541
|
+
name: "Express/Fastify Route Without Auth Middleware",
|
|
542
|
+
severity: "high",
|
|
543
|
+
category: "web-owasp",
|
|
544
|
+
pattern: /(?:app|router|fastify)\s*\.(?:post|put|patch|delete)\s*\(\s*["']\/(?!health|webhook|public|auth|login|register|signup|stripe)[^"']*["']\s*,\s*(?:async\s+)?(?:\([^{)]*\)|(?:req|request|ctx))\s*(?:=>|{)/,
|
|
545
|
+
fileFilter: /(?:route|controller|handler)/i,
|
|
546
|
+
owasp: "A01:2021",
|
|
547
|
+
},
|
|
548
|
+
// CORS configured with wildcard in Express/Fastify
|
|
549
|
+
{
|
|
550
|
+
id: 50,
|
|
551
|
+
name: "CORS Wildcard in Express/Fastify",
|
|
552
|
+
severity: "medium",
|
|
553
|
+
category: "web-owasp",
|
|
554
|
+
pattern: /cors\s*\(\s*\{[^}]*origin\s*:\s*(?:true|["']\*["'])/,
|
|
555
|
+
owasp: "A05:2021",
|
|
556
|
+
},
|
|
557
|
+
// Error handler leaking stack traces
|
|
558
|
+
{
|
|
559
|
+
id: 173,
|
|
560
|
+
name: "Error Handler Exposes Stack Trace",
|
|
561
|
+
severity: "medium",
|
|
562
|
+
category: "error-logging",
|
|
563
|
+
pattern: /(?:res|reply)\.(?:status|code)\s*\(\s*5\d\d\s*\)\.?\s*(?:json|send)\s*\(\s*\{[^}]*(?:stack|\.message|err\.toString)/,
|
|
564
|
+
fileFilter: /(?:route|controller|handler|middleware|error)/i,
|
|
565
|
+
},
|
|
566
|
+
// Prisma $queryRaw / $executeRaw with template literals (SQL injection)
|
|
567
|
+
{
|
|
568
|
+
id: 5,
|
|
569
|
+
name: "Prisma Raw Query with Template Literal",
|
|
570
|
+
severity: "critical",
|
|
571
|
+
category: "web-owasp",
|
|
572
|
+
pattern: /prisma\.\$(?:queryRaw|executeRaw)\s*\(\s*`[^`]*\$\{/,
|
|
573
|
+
owasp: "A03:2021",
|
|
574
|
+
},
|
|
575
|
+
// Missing HTTPS redirect
|
|
576
|
+
{
|
|
577
|
+
id: 66,
|
|
578
|
+
name: "Server Without HTTPS/TLS Configuration",
|
|
579
|
+
severity: "medium",
|
|
580
|
+
category: "headers",
|
|
581
|
+
pattern: /(?:\.listen\s*\(\s*\{[^}]*host\s*:\s*["']0\.0\.0\.0|http\.createServer)(?:(?!https|ssl|tls|cert)[\s\S]){0,500}/,
|
|
582
|
+
// Exclude Next.js projects (behind Vercel/reverse proxy), Docker workers, etc.
|
|
583
|
+
fileFilter: /(?:server|index|app)\.[tj]s$/,
|
|
584
|
+
confidence: "low",
|
|
585
|
+
// Suppressed when project has Dockerfile, vercel.json, railway.json, or next.config
|
|
586
|
+
// (these deploy behind reverse proxies that handle TLS)
|
|
587
|
+
suppressedByMiddleware: true,
|
|
588
|
+
},
|
|
589
|
+
// Sensitive data in URL params
|
|
590
|
+
{
|
|
591
|
+
id: 93,
|
|
592
|
+
name: "Sensitive Data in URL Path or Query",
|
|
593
|
+
severity: "medium",
|
|
594
|
+
category: "frontend",
|
|
595
|
+
pattern: /(?:redirect|url|href|location)\s*[=:]\s*[`"'][^`"']*(?:token=|password=|secret=|key=|api_key=|apiKey=)/i,
|
|
596
|
+
owasp: "A07:2021",
|
|
597
|
+
},
|
|
598
|
+
// Console.log in production routes
|
|
599
|
+
{
|
|
600
|
+
id: 172,
|
|
601
|
+
name: "Console.log with Sensitive Data in Route",
|
|
602
|
+
severity: "medium",
|
|
603
|
+
category: "error-logging",
|
|
604
|
+
pattern: /console\.log\s*\([^)]*(?:password|token|secret|apiKey|api_key|authorization|cookie)/i,
|
|
605
|
+
fileFilter: /(?:route|controller|handler|middleware)/i,
|
|
606
|
+
},
|
|
607
|
+
// =============================================
|
|
608
|
+
// AI / LLM Security
|
|
609
|
+
// =============================================
|
|
610
|
+
{
|
|
611
|
+
id: 75,
|
|
612
|
+
name: "Prompt Injection — User Input in LLM Prompt",
|
|
613
|
+
severity: "high",
|
|
614
|
+
category: "ai-llm",
|
|
615
|
+
// Match user input flowing into a prompt/messages template or a messages
|
|
616
|
+
// array. The generic `content` variable name is intentionally NOT a
|
|
617
|
+
// trigger: it collides with HTML/email/file "content" everywhere.
|
|
618
|
+
pattern: /(?:prompt|messages)\s*[=:]\s*`[^`]*\$\{[^}]*(?:req\.|body\.|query\.|params\.|userInput|userMessage|userPrompt)|(?:messages\.push|\.concat)\s*\(\s*\{[^}]*(?:req\.|body\.|query\.|userInput|userMessage|userPrompt)/,
|
|
619
|
+
// Only fire in files that actually talk to an LLM. Without this guard the
|
|
620
|
+
// rule misfired on email/HTML templates that interpolate user input.
|
|
621
|
+
requiresNearby: /anthropic|openai|\bllm\b|\bclaude\b|\bgpt-?\d|langchain|cohere|mistral|ollama|messages\.create|chat\.completions|completions\.create|generateText|streamText|role:\s*["'](?:system|user|assistant)["']/i,
|
|
622
|
+
owasp: "A03:2021",
|
|
623
|
+
},
|
|
624
|
+
{
|
|
625
|
+
id: 53,
|
|
626
|
+
name: "PII Sent to External AI API",
|
|
627
|
+
severity: "high",
|
|
628
|
+
category: "ai-llm",
|
|
629
|
+
// Only flag when PII fields are sent to known AI/LLM providers.
|
|
630
|
+
// Internal APIs, email services (Resend, SendGrid), and non-AI fetches are safe.
|
|
631
|
+
pattern: /(?:anthropic|openai|cohere|replicate|huggingface|ai\.run|completions\.create|messages\.create|chat\.completions)[\s\S]{0,200}(?:email|password|ssn|creditCard|phoneNumber|dateOfBirth|socialSecurity)/i,
|
|
632
|
+
confidence: "medium",
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: 53,
|
|
636
|
+
name: "AI API Key Exposed in Frontend",
|
|
637
|
+
severity: "critical",
|
|
638
|
+
category: "ai-llm",
|
|
639
|
+
pattern: /(?:NEXT_PUBLIC_|window\.|globalThis\.)(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|AI_API_KEY|CLAUDE_API_KEY)/,
|
|
640
|
+
owasp: "A07:2021",
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
id: 61,
|
|
644
|
+
name: "AI-Generated Code Executed Dynamically",
|
|
645
|
+
severity: "critical",
|
|
646
|
+
category: "ai-llm",
|
|
647
|
+
pattern: /(?:eval|Function|vm\.run)\s*\([^)]*(?:completion|response|aiOutput|generated|llmResult|chat)/i,
|
|
648
|
+
owasp: "A03:2021",
|
|
649
|
+
},
|
|
650
|
+
// =============================================
|
|
651
|
+
// Database Connection Security
|
|
652
|
+
// =============================================
|
|
653
|
+
{
|
|
654
|
+
id: 101,
|
|
655
|
+
name: "Database Connection Without SSL",
|
|
656
|
+
severity: "high",
|
|
657
|
+
category: "database-connection",
|
|
658
|
+
pattern: /(?:createPool|createConnection|new\s+(?:Pool|Client|Sequelize|Knex))\s*\(\s*\{(?:(?!ssl|sslmode)[\s\S]){0,500}\}\s*\)/,
|
|
659
|
+
fileFilter: /(?:db|database|prisma|knex|sequelize|connection|pool)/i,
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
id: 148,
|
|
663
|
+
name: "Database Publicly Accessible (0.0.0.0 Bind)",
|
|
664
|
+
severity: "critical",
|
|
665
|
+
category: "database-connection",
|
|
666
|
+
pattern: /(?:bind_address|host|listen)\s*[=:]\s*["']0\.0\.0\.0["']/,
|
|
667
|
+
fileFilter: /(?:config|docker|compose|\.env|yml|yaml)/,
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
id: 101,
|
|
671
|
+
name: "Default Database Credentials",
|
|
672
|
+
severity: "critical",
|
|
673
|
+
category: "database-connection",
|
|
674
|
+
pattern: /(?:postgres:postgres|root:root|admin:admin|user:password|sa:sa|mysql:mysql)@/,
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
id: 101,
|
|
678
|
+
name: "Database Connection String with Inline Password",
|
|
679
|
+
severity: "high",
|
|
680
|
+
category: "database-connection",
|
|
681
|
+
pattern: /(?:postgresql|mysql|mongodb|redis|amqp):\/\/\w+:[^@\s"']{3,}@(?!localhost|127\.0\.0\.1)/,
|
|
682
|
+
},
|
|
683
|
+
{
|
|
684
|
+
id: 101,
|
|
685
|
+
name: "MongoDB Connection Without Authentication",
|
|
686
|
+
severity: "critical",
|
|
687
|
+
category: "database-connection",
|
|
688
|
+
pattern: /mongodb:\/\/(?!.*(?:authSource|auth_source))(?:\w+@)?(?:(?!localhost|127\.0\.0\.1)[\w.-]+)/,
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id: 101,
|
|
692
|
+
name: "Redis Without Authentication",
|
|
693
|
+
severity: "high",
|
|
694
|
+
category: "database-connection",
|
|
695
|
+
pattern: /(?:redis:\/\/(?!.*:.*@)|new\s+Redis\s*\(\s*\{(?:(?!password|auth)[\s\S]){0,300}\})(?:(?!localhost|127\.0\.0\.1)[\s\S]){0,100}/,
|
|
696
|
+
},
|
|
697
|
+
{
|
|
698
|
+
id: 53,
|
|
699
|
+
name: "Database Credentials Logged",
|
|
700
|
+
severity: "high",
|
|
701
|
+
category: "database-connection",
|
|
702
|
+
pattern: /console\.(?:log|info|warn|error)\s*\([^)]*(?:DATABASE_URL|DB_PASSWORD|DB_HOST|connectionString|SUPABASE_SERVICE_ROLE)/,
|
|
703
|
+
},
|
|
704
|
+
{
|
|
705
|
+
id: 2,
|
|
706
|
+
name: "Database SSL Certificate Validation Disabled",
|
|
707
|
+
severity: "high",
|
|
708
|
+
category: "database-connection",
|
|
709
|
+
pattern: /(?:ssl|tls)\s*:\s*\{[\s\S]*?rejectUnauthorized\s*:\s*false/,
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
id: 101,
|
|
713
|
+
name: "Hardcoded Database Host",
|
|
714
|
+
severity: "medium",
|
|
715
|
+
category: "database-connection",
|
|
716
|
+
pattern: /(?:host|hostname)\s*[=:]\s*["'](?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|[\w-]+\.(?:rds\.amazonaws\.com|database\.azure\.com|cloudsql\.google\.com|supabase\.co))["']/,
|
|
717
|
+
},
|
|
718
|
+
// =============================================
|
|
719
|
+
// Cloud Provider Security
|
|
720
|
+
// =============================================
|
|
721
|
+
{
|
|
722
|
+
id: 4,
|
|
723
|
+
name: "S3 Bucket Public Access",
|
|
724
|
+
severity: "critical",
|
|
725
|
+
category: "cloud",
|
|
726
|
+
pattern: /(?:BlockPublicAccess\s*[=:]\s*false|ACL\s*[=:]\s*["']public-read|s3:PutBucketPolicy[\s\S]{0,200}["']\*["'])/i,
|
|
727
|
+
owasp: "A05:2021",
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
id: 10,
|
|
731
|
+
name: "Cloud Metadata SSRF Vector",
|
|
732
|
+
severity: "critical",
|
|
733
|
+
category: "cloud",
|
|
734
|
+
pattern: /(?:169\.254\.169\.254|metadata\.google\.internal|169\.254\.170\.2)/,
|
|
735
|
+
owasp: "A10:2021",
|
|
736
|
+
},
|
|
737
|
+
{
|
|
738
|
+
id: 53,
|
|
739
|
+
name: "AWS Credentials Hardcoded",
|
|
740
|
+
severity: "critical",
|
|
741
|
+
category: "cloud",
|
|
742
|
+
pattern: /(?:aws_access_key_id|aws_secret_access_key)\s*[=:]\s*["'][A-Za-z0-9/+=]{20,}["']/i,
|
|
743
|
+
},
|
|
744
|
+
{
|
|
745
|
+
id: 1,
|
|
746
|
+
name: "IAM Policy Overly Permissive",
|
|
747
|
+
severity: "high",
|
|
748
|
+
category: "cloud",
|
|
749
|
+
pattern: /["']Action["']\s*:\s*["']\*["'][\s\S]{0,100}["']Resource["']\s*:\s*["']\*["']/,
|
|
750
|
+
fileFilter: /(?:policy|iam|role|permission|terraform|cdk|cloudformation)/i,
|
|
751
|
+
},
|
|
752
|
+
{
|
|
753
|
+
id: 1,
|
|
754
|
+
name: "Cloud Function Without Auth",
|
|
755
|
+
severity: "high",
|
|
756
|
+
category: "cloud",
|
|
757
|
+
pattern: /(?:--allow-unauthenticated|allUsers|allAuthenticatedUsers|FunctionUrlAuthType\.NONE)/,
|
|
758
|
+
fileFilter: /(?:serverless|template|cdk|terraform|yml|yaml)/,
|
|
759
|
+
},
|
|
760
|
+
// =============================================
|
|
761
|
+
// Infrastructure as Code
|
|
762
|
+
// =============================================
|
|
763
|
+
{
|
|
764
|
+
id: 53,
|
|
765
|
+
name: "Terraform State File Committed",
|
|
766
|
+
severity: "critical",
|
|
767
|
+
category: "iac",
|
|
768
|
+
pattern: /terraform\.tfstate|"serial"\s*:\s*\d+[\s\S]{0,100}"lineage"/,
|
|
769
|
+
fileFilter: /(?:tfstate|terraform)/,
|
|
770
|
+
},
|
|
771
|
+
{
|
|
772
|
+
id: 53,
|
|
773
|
+
name: "Infrastructure Credentials in Code",
|
|
774
|
+
severity: "critical",
|
|
775
|
+
category: "iac",
|
|
776
|
+
pattern: /(?:access_key|secret_key|api_token|subscription_id)\s*=\s*["'][^"']{10,}["']/,
|
|
777
|
+
fileFilter: /\.(?:tf|hcl|yaml|yml)$/,
|
|
778
|
+
},
|
|
779
|
+
{
|
|
780
|
+
id: 148,
|
|
781
|
+
name: "Kubernetes Privileged Container",
|
|
782
|
+
severity: "high",
|
|
783
|
+
category: "iac",
|
|
784
|
+
pattern: /privileged\s*:\s*true/,
|
|
785
|
+
fileFilter: /\.(?:yaml|yml)$/,
|
|
786
|
+
},
|
|
787
|
+
{
|
|
788
|
+
id: 53,
|
|
789
|
+
name: "Helm/K8s Secrets in Plain Values",
|
|
790
|
+
severity: "high",
|
|
791
|
+
category: "iac",
|
|
792
|
+
pattern: /(?:password|secret|token|apiKey)\s*:\s*["']?[A-Za-z0-9+/=]{8,}["']?/,
|
|
793
|
+
fileFilter: /values\.ya?ml$/,
|
|
794
|
+
},
|
|
795
|
+
// =============================================
|
|
796
|
+
// CI/CD Pipeline Security
|
|
797
|
+
// =============================================
|
|
798
|
+
{
|
|
799
|
+
id: 140,
|
|
800
|
+
name: "Unpinned GitHub Action",
|
|
801
|
+
severity: "high",
|
|
802
|
+
category: "cicd",
|
|
803
|
+
pattern: /uses\s*:\s*[\w-]+\/[\w-]+@(?:main|master|latest|v\d+)\s*$/m,
|
|
804
|
+
fileFilter: /\.github\/workflows\//,
|
|
805
|
+
},
|
|
806
|
+
{
|
|
807
|
+
id: 75,
|
|
808
|
+
name: "GitHub Actions Script Injection",
|
|
809
|
+
severity: "critical",
|
|
810
|
+
category: "cicd",
|
|
811
|
+
pattern: /run\s*:.*\$\{\{\s*github\.event\.(?:issue|pull_request|comment|review|discussion)\.(?:title|body|head\.ref)/,
|
|
812
|
+
fileFilter: /\.github\/workflows\//,
|
|
813
|
+
owasp: "A03:2021",
|
|
814
|
+
},
|
|
815
|
+
{
|
|
816
|
+
id: 133,
|
|
817
|
+
name: "Workflow Permissions Too Broad",
|
|
818
|
+
severity: "medium",
|
|
819
|
+
category: "cicd",
|
|
820
|
+
pattern: /permissions\s*:\s*write-all/,
|
|
821
|
+
fileFilter: /\.github\/workflows\//,
|
|
822
|
+
},
|
|
823
|
+
// =============================================
|
|
824
|
+
// WebSocket & Real-time
|
|
825
|
+
// =============================================
|
|
826
|
+
{
|
|
827
|
+
id: 1,
|
|
828
|
+
name: "WebSocket Missing Origin Validation",
|
|
829
|
+
severity: "high",
|
|
830
|
+
category: "websocket",
|
|
831
|
+
pattern: /(?:ws\.on|wss\.on|io\.on)\s*\(\s*["']connection["']\s*,(?:(?!verifyClient|origin|allowRequest)[\s\S]){0,500}/,
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
id: 197,
|
|
835
|
+
name: "WebSocket Without Message Rate Limiting",
|
|
836
|
+
severity: "medium",
|
|
837
|
+
category: "websocket",
|
|
838
|
+
pattern: /(?:ws|socket)\.on\s*\(\s*["']message["']\s*,(?:(?!rateLimit|throttle|bucket)[\s\S]){0,500}/,
|
|
839
|
+
},
|
|
840
|
+
// =============================================
|
|
841
|
+
// 1. ReDoS — Vulnerable Static Regex Patterns
|
|
842
|
+
// Detect regex with nested quantifiers that cause catastrophic backtracking.
|
|
843
|
+
// These are language-agnostic since all regex engines suffer from this.
|
|
844
|
+
// =============================================
|
|
845
|
+
{
|
|
846
|
+
id: 91,
|
|
847
|
+
name: "ReDoS — Nested Quantifiers in Regex",
|
|
848
|
+
severity: "medium",
|
|
849
|
+
category: "redos",
|
|
850
|
+
pattern: /(?:new\s+RegExp|re\.compile|regexp\.MustCompile|Pattern\.compile|preg_match|Regex)\s*\(\s*["'`\/](?:[^"'`\/]*(?:\([^)]*[+*]\)[+*]|\([^)]*\|[^)]*\)[+*]))["'`\/]/,
|
|
851
|
+
owasp: "A06:2021",
|
|
852
|
+
confidence: "medium",
|
|
853
|
+
},
|
|
854
|
+
{
|
|
855
|
+
id: 91,
|
|
856
|
+
name: "ReDoS — Regex with Overlapping Alternation",
|
|
857
|
+
severity: "medium",
|
|
858
|
+
category: "redos",
|
|
859
|
+
pattern: /(?:\/|["'`])(?:[^\/"'`]*(?:\(\?:[^)]+\|[^)]+\)\+|\([^)]+\)\{\d+,\}))(?:\/|["'`])/,
|
|
860
|
+
fileFilter: /(?:\/api\/|\/server|\.server\.|route\.|controller|handler|middleware|validator|sanitiz)/i,
|
|
861
|
+
owasp: "A06:2021",
|
|
862
|
+
confidence: "low",
|
|
863
|
+
},
|
|
864
|
+
// =============================================
|
|
865
|
+
// 2. Sensitive Data in Logs — Multi-language
|
|
866
|
+
// Detect logging calls that include sensitive variable names.
|
|
867
|
+
// =============================================
|
|
868
|
+
// Python: print/logging with sensitive data
|
|
869
|
+
{
|
|
870
|
+
id: 172,
|
|
871
|
+
name: "Sensitive Data Logged (Python)",
|
|
872
|
+
severity: "medium",
|
|
873
|
+
category: "error-logging",
|
|
874
|
+
pattern: /(?:print\s*\(|logging\.(?:info|warning|error|debug|critical)\s*\(|logger\.(?:info|warning|error|debug)\s*\()[^)]*(?:password|token|secret|api_key|apikey|authorization|credit_card|ssn|private_key)/i,
|
|
875
|
+
fileFilter: /\.py$/,
|
|
876
|
+
owasp: "A09:2021",
|
|
877
|
+
},
|
|
878
|
+
// Go: log/fmt with sensitive data
|
|
879
|
+
{
|
|
880
|
+
id: 172,
|
|
881
|
+
name: "Sensitive Data Logged (Go)",
|
|
882
|
+
severity: "medium",
|
|
883
|
+
category: "error-logging",
|
|
884
|
+
pattern: /(?:log\.(?:Print|Printf|Println|Fatal|Fatalf)\s*\(|fmt\.(?:Print|Printf|Println|Fprintf)\s*\()[^)]*(?:password|token|secret|apiKey|api_key|authorization|creditCard|ssn|privateKey)/i,
|
|
885
|
+
fileFilter: /\.go$/,
|
|
886
|
+
owasp: "A09:2021",
|
|
887
|
+
},
|
|
888
|
+
// Java: logger/System.out with sensitive data
|
|
889
|
+
{
|
|
890
|
+
id: 172,
|
|
891
|
+
name: "Sensitive Data Logged (Java)",
|
|
892
|
+
severity: "medium",
|
|
893
|
+
category: "error-logging",
|
|
894
|
+
pattern: /(?:logger\.(?:info|warn|error|debug)\s*\(|System\.out\.print(?:ln)?\s*\(|Log\.(?:d|e|i|w|v)\s*\()[^)]*(?:password|token|secret|apiKey|api_key|authorization|creditCard|ssn|privateKey)/i,
|
|
895
|
+
fileFilter: /\.(?:java|kt)$/,
|
|
896
|
+
owasp: "A09:2021",
|
|
897
|
+
},
|
|
898
|
+
// PHP: error_log/var_dump with sensitive data
|
|
899
|
+
{
|
|
900
|
+
id: 172,
|
|
901
|
+
name: "Sensitive Data Logged (PHP)",
|
|
902
|
+
severity: "medium",
|
|
903
|
+
category: "error-logging",
|
|
904
|
+
pattern: /(?:error_log\s*\(|var_dump\s*\(|print_r\s*\(|Log::(?:info|warning|error|debug)\s*\()[^)]*(?:password|token|secret|api_key|apikey|authorization|credit_card|ssn|private_key)/i,
|
|
905
|
+
fileFilter: /\.php$/,
|
|
906
|
+
owasp: "A09:2021",
|
|
907
|
+
},
|
|
908
|
+
// C#: logger/Console with sensitive data
|
|
909
|
+
{
|
|
910
|
+
id: 172,
|
|
911
|
+
name: "Sensitive Data Logged (C#)",
|
|
912
|
+
severity: "medium",
|
|
913
|
+
category: "error-logging",
|
|
914
|
+
pattern: /(?:Console\.Write(?:Line)?\s*\(|_logger\.Log(?:Information|Warning|Error|Debug)?\s*\(|Debug\.(?:Log|Write)\s*\()[^)]*(?:password|token|secret|apiKey|api_key|authorization|creditCard|ssn|privateKey)/i,
|
|
915
|
+
fileFilter: /\.cs$/,
|
|
916
|
+
owasp: "A09:2021",
|
|
917
|
+
},
|
|
918
|
+
// =============================================
|
|
919
|
+
// 3. Insecure Randomness — Multi-language
|
|
920
|
+
// Detect non-cryptographic RNG used for security-sensitive values.
|
|
921
|
+
// =============================================
|
|
922
|
+
// Python: random module for security
|
|
923
|
+
{
|
|
924
|
+
id: 125,
|
|
925
|
+
name: "Insecure Random for Security Purpose (Python)",
|
|
926
|
+
severity: "high",
|
|
927
|
+
category: "cryptography",
|
|
928
|
+
pattern: /(?:token|secret|key|session|nonce|salt|otp|password|code)[\s\S]{0,40}random\.(?:random|randint|choice|randrange|getrandbits)\s*\(/i,
|
|
929
|
+
fileFilter: /\.py$/,
|
|
930
|
+
owasp: "A02:2021",
|
|
931
|
+
},
|
|
932
|
+
// Go: math/rand for security
|
|
933
|
+
{
|
|
934
|
+
id: 125,
|
|
935
|
+
name: "Insecure Random for Security Purpose (Go)",
|
|
936
|
+
severity: "high",
|
|
937
|
+
category: "cryptography",
|
|
938
|
+
pattern: /(?:token|secret|key|session|nonce|salt|otp|password)[\s\S]{0,40}(?:rand\.(?:Int|Intn|Float|Read|New)\s*\(|"math\/rand")/i,
|
|
939
|
+
fileFilter: /\.go$/,
|
|
940
|
+
owasp: "A02:2021",
|
|
941
|
+
},
|
|
942
|
+
// Java: java.util.Random for security
|
|
943
|
+
{
|
|
944
|
+
id: 125,
|
|
945
|
+
name: "Insecure Random for Security Purpose (Java)",
|
|
946
|
+
severity: "high",
|
|
947
|
+
category: "cryptography",
|
|
948
|
+
pattern: /(?:token|secret|key|session|nonce|salt|otp|password)[\s\S]{0,40}(?:new\s+Random\s*\(|ThreadLocalRandom\.current\(\)\.next|Random\(\)\.next)/i,
|
|
949
|
+
fileFilter: /\.(?:java|kt)$/,
|
|
950
|
+
owasp: "A02:2021",
|
|
951
|
+
},
|
|
952
|
+
// PHP: rand/mt_rand for security
|
|
953
|
+
{
|
|
954
|
+
id: 125,
|
|
955
|
+
name: "Insecure Random for Security Purpose (PHP)",
|
|
956
|
+
severity: "high",
|
|
957
|
+
category: "cryptography",
|
|
958
|
+
pattern: /(?:token|secret|key|session|nonce|salt|otp|password)[\s\S]{0,40}(?:rand\s*\(|mt_rand\s*\(|array_rand\s*\(|shuffle\s*\()/i,
|
|
959
|
+
fileFilter: /\.php$/,
|
|
960
|
+
owasp: "A02:2021",
|
|
961
|
+
},
|
|
962
|
+
// C#: System.Random for security
|
|
963
|
+
{
|
|
964
|
+
id: 125,
|
|
965
|
+
name: "Insecure Random for Security Purpose (C#)",
|
|
966
|
+
severity: "high",
|
|
967
|
+
category: "cryptography",
|
|
968
|
+
pattern: /(?:token|secret|key|session|nonce|salt|otp|password)[\s\S]{0,40}(?:new\s+Random\s*\(|Random\.Shared\.Next)/i,
|
|
969
|
+
fileFilter: /\.cs$/,
|
|
970
|
+
owasp: "A02:2021",
|
|
971
|
+
},
|
|
972
|
+
// =============================================
|
|
973
|
+
// 4. Hardcoded Internal IPs & Staging URLs
|
|
974
|
+
// Detect private IPs and dev/staging URLs in production code.
|
|
975
|
+
// =============================================
|
|
976
|
+
{
|
|
977
|
+
id: 4,
|
|
978
|
+
name: "Hardcoded Internal IP Address",
|
|
979
|
+
severity: "low",
|
|
980
|
+
category: "misconfiguration",
|
|
981
|
+
pattern: /["'`]https?:\/\/(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2[0-9]|3[01])\.\d{1,3}\.\d{1,3})(?::\d+)?/,
|
|
982
|
+
owasp: "A05:2021",
|
|
983
|
+
confidence: "medium",
|
|
984
|
+
},
|
|
985
|
+
{
|
|
986
|
+
id: 4,
|
|
987
|
+
name: "Hardcoded Localhost URL in Source",
|
|
988
|
+
severity: "low",
|
|
989
|
+
category: "misconfiguration",
|
|
990
|
+
pattern: /["'`]https?:\/\/(?:localhost|127\.0\.0\.1)(?::\d+)?(?:\/[^"'`]*)?["'`]/,
|
|
991
|
+
// Only flag in source files that are likely production code, not config/env/docker
|
|
992
|
+
fileFilter: /(?:\/api\/|\/lib\/|\/src\/|\/app\/|\/server|route\.|controller|service|handler)/i,
|
|
993
|
+
owasp: "A05:2021",
|
|
994
|
+
confidence: "low",
|
|
995
|
+
},
|
|
996
|
+
{
|
|
997
|
+
id: 4,
|
|
998
|
+
name: "Staging/Dev URL Hardcoded in Source",
|
|
999
|
+
severity: "low",
|
|
1000
|
+
category: "misconfiguration",
|
|
1001
|
+
pattern: /["'`]https?:\/\/(?:staging|dev|test|qa|uat|sandbox|preprod|stg)[\.-]/i,
|
|
1002
|
+
// Exclude test/config files where these URLs are expected
|
|
1003
|
+
fileFilter: /(?:\/api\/|\/lib\/|\/src\/|\/app\/|\/server|route\.|controller|service|handler)/i,
|
|
1004
|
+
owasp: "A05:2021",
|
|
1005
|
+
confidence: "low",
|
|
1006
|
+
},
|
|
1007
|
+
// =============================================
|
|
1008
|
+
// 5. Mass Assignment — Multi-language
|
|
1009
|
+
// Detect request body spread directly into database operations.
|
|
1010
|
+
// =============================================
|
|
1011
|
+
// Python: **request.data or Model(**data) without whitelist
|
|
1012
|
+
{
|
|
1013
|
+
id: 98,
|
|
1014
|
+
name: "Mass Assignment — Request Data to Model (Python)",
|
|
1015
|
+
severity: "high",
|
|
1016
|
+
category: "api",
|
|
1017
|
+
pattern: /(?:Model|\.objects\.create|\.objects\.update|serializer\.save|\.update_or_create)\s*\(\s*\*\*\s*(?:request\.data|request\.POST|data|kwargs)/,
|
|
1018
|
+
fileFilter: /\.py$/,
|
|
1019
|
+
owasp: "A08:2021",
|
|
1020
|
+
},
|
|
1021
|
+
// Go: json.Decode directly into DB model without field whitelist
|
|
1022
|
+
{
|
|
1023
|
+
id: 98,
|
|
1024
|
+
name: "Mass Assignment — JSON Decode to Model (Go)",
|
|
1025
|
+
severity: "high",
|
|
1026
|
+
category: "api",
|
|
1027
|
+
pattern: /json\.NewDecoder\s*\([^)]*\)\.Decode\s*\(\s*&\s*\w+\s*\)[\s\S]{0,100}(?:\.Create|\.Save|\.Update|\.Insert|db\.Exec)/,
|
|
1028
|
+
fileFilter: /\.go$/,
|
|
1029
|
+
owasp: "A08:2021",
|
|
1030
|
+
confidence: "low",
|
|
1031
|
+
},
|
|
1032
|
+
// PHP: $request->all() passed to create/update
|
|
1033
|
+
{
|
|
1034
|
+
id: 98,
|
|
1035
|
+
name: "Mass Assignment — Request All to Model (PHP)",
|
|
1036
|
+
severity: "high",
|
|
1037
|
+
category: "api",
|
|
1038
|
+
pattern: /(?:::create|::update|->fill|->update|->insert)\s*\(\s*\$request->all\s*\(\s*\)/,
|
|
1039
|
+
fileFilter: /\.php$/,
|
|
1040
|
+
owasp: "A08:2021",
|
|
1041
|
+
},
|
|
1042
|
+
// Java: @ModelAttribute without @Valid or BindingResult
|
|
1043
|
+
{
|
|
1044
|
+
id: 98,
|
|
1045
|
+
name: "Mass Assignment — Unvalidated Model Binding (Java)",
|
|
1046
|
+
severity: "medium",
|
|
1047
|
+
category: "api",
|
|
1048
|
+
pattern: /@ModelAttribute\s+(?:(?!@Valid|BindingResult)[\s\S]){0,100}(?:save|update|create|persist)/,
|
|
1049
|
+
fileFilter: /\.(?:java|kt)$/,
|
|
1050
|
+
owasp: "A08:2021",
|
|
1051
|
+
confidence: "low",
|
|
1052
|
+
},
|
|
1053
|
+
];
|
|
1054
|
+
export function analyzePatterns(files) {
|
|
1055
|
+
const allFindings = [];
|
|
1056
|
+
// Pre-scan: detect project-level security infrastructure
|
|
1057
|
+
const projectFeatures = detectProjectFeatures(files);
|
|
1058
|
+
for (const [filePath, content] of files) {
|
|
1059
|
+
for (const rule of PATTERN_RULES) {
|
|
1060
|
+
// Apply file filter if defined
|
|
1061
|
+
if (rule.fileFilter && !rule.fileFilter.test(filePath))
|
|
1062
|
+
continue;
|
|
1063
|
+
// Skip non-source files
|
|
1064
|
+
if (!isSourceFile(filePath))
|
|
1065
|
+
continue;
|
|
1066
|
+
// Skip rules suppressed by project-level middleware/infrastructure
|
|
1067
|
+
if (rule.suppressedByMiddleware && projectFeatures.hasMiddlewareRateLimit)
|
|
1068
|
+
continue;
|
|
1069
|
+
// Require corroborating context in the same file, if the rule demands it
|
|
1070
|
+
if (rule.requiresNearby) {
|
|
1071
|
+
rule.requiresNearby.lastIndex = 0;
|
|
1072
|
+
if (!rule.requiresNearby.test(content))
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1075
|
+
// Reset regex state
|
|
1076
|
+
rule.pattern.lastIndex = 0;
|
|
1077
|
+
const match = rule.pattern.exec(content);
|
|
1078
|
+
if (match) {
|
|
1079
|
+
const lineNumber = getLineNumber(content, match.index);
|
|
1080
|
+
const snippet = getSnippet(content, lineNumber);
|
|
1081
|
+
allFindings.push({
|
|
1082
|
+
vulnerability_id: rule.id,
|
|
1083
|
+
severity: rule.severity,
|
|
1084
|
+
category: rule.category,
|
|
1085
|
+
title: rule.name,
|
|
1086
|
+
file_path: filePath,
|
|
1087
|
+
line_number: lineNumber,
|
|
1088
|
+
code_snippet: snippet,
|
|
1089
|
+
owasp_ref: rule.owasp,
|
|
1090
|
+
status: "open",
|
|
1091
|
+
confidence: rule.confidence ?? "medium",
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
// File-level special checks — dangerouslySetInnerHTML with file-level context
|
|
1097
|
+
runDangerousHtmlChecks(files, allFindings);
|
|
1098
|
+
// File-level absence checks — detect when a project is MISSING security features
|
|
1099
|
+
runAbsenceChecks(files, allFindings);
|
|
1100
|
+
return allFindings;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Detect project-level security infrastructure that makes per-route checks redundant.
|
|
1104
|
+
* For example, if middleware.ts handles rate limiting, individual API routes
|
|
1105
|
+
* don't need their own rate limiting.
|
|
1106
|
+
*/
|
|
1107
|
+
function detectProjectFeatures(files) {
|
|
1108
|
+
let hasMiddlewareRateLimit = false;
|
|
1109
|
+
let hasReverseProxy = false;
|
|
1110
|
+
let hasGlobalAuth = false;
|
|
1111
|
+
let hasGlobalCors = false;
|
|
1112
|
+
let hasHelmet = false;
|
|
1113
|
+
let hasInputValidation = false;
|
|
1114
|
+
for (const [filePath, content] of files) {
|
|
1115
|
+
const fileName = filePath.split("/").pop() || "";
|
|
1116
|
+
// === Rate Limiting Detection ===
|
|
1117
|
+
// Next.js middleware
|
|
1118
|
+
if (/middleware\.[tj]sx?$/.test(fileName) || /rate.?limit/i.test(fileName)) {
|
|
1119
|
+
if (/rateLimit|rate.?limit|throttle|limiter|req.*per.*(?:min|sec|hour|window)/i.test(content)) {
|
|
1120
|
+
hasMiddlewareRateLimit = true;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
// Express/Fastify global middleware
|
|
1124
|
+
if (/app\.use\s*\(\s*(?:rateLimit|rateLimiter|createRateLimiter|slowDown)/i.test(content)) {
|
|
1125
|
+
hasMiddlewareRateLimit = true;
|
|
1126
|
+
}
|
|
1127
|
+
// Django rate limiting
|
|
1128
|
+
if (/django[_-]ratelimit|rest_framework\.throttling|DEFAULT_THROTTLE_CLASSES/i.test(content)) {
|
|
1129
|
+
hasMiddlewareRateLimit = true;
|
|
1130
|
+
}
|
|
1131
|
+
// Spring Boot rate limiting
|
|
1132
|
+
if (/RateLimiter|bucket4j|resilience4j|Bucket4j|@RateLimited/i.test(content)) {
|
|
1133
|
+
hasMiddlewareRateLimit = true;
|
|
1134
|
+
}
|
|
1135
|
+
// Laravel rate limiting
|
|
1136
|
+
if (/RateLimiter::for|ThrottleRequests|throttle:/i.test(content)) {
|
|
1137
|
+
hasMiddlewareRateLimit = true;
|
|
1138
|
+
}
|
|
1139
|
+
// Go rate limiting
|
|
1140
|
+
if (/golang\.org\/x\/time\/rate|rate\.NewLimiter|httprate|tollbooth/i.test(content)) {
|
|
1141
|
+
hasMiddlewareRateLimit = true;
|
|
1142
|
+
}
|
|
1143
|
+
// ASP.NET rate limiting
|
|
1144
|
+
if (/AddRateLimiter|UseRateLimiter|RateLimiting/i.test(content)) {
|
|
1145
|
+
hasMiddlewareRateLimit = true;
|
|
1146
|
+
}
|
|
1147
|
+
// === Global Auth Detection ===
|
|
1148
|
+
// Next.js / Express middleware auth
|
|
1149
|
+
if (/middleware\.[tj]sx?$/.test(fileName)) {
|
|
1150
|
+
if (/auth|session|getUser|getSession|verifyAuth|requireAuth/i.test(content)) {
|
|
1151
|
+
hasGlobalAuth = true;
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
// Express passport / auth middleware
|
|
1155
|
+
if (/app\.use\s*\(\s*(?:passport|authenticate|requireAuth|verifyToken|authMiddleware|isAuthenticated)/i.test(content)) {
|
|
1156
|
+
hasGlobalAuth = true;
|
|
1157
|
+
}
|
|
1158
|
+
// Django auth middleware
|
|
1159
|
+
if (/AuthenticationMiddleware|LoginRequiredMiddleware|REST_FRAMEWORK.*DEFAULT_PERMISSION_CLASSES.*IsAuthenticated/i.test(content)) {
|
|
1160
|
+
hasGlobalAuth = true;
|
|
1161
|
+
}
|
|
1162
|
+
// Spring Security
|
|
1163
|
+
if (/@EnableWebSecurity|@EnableGlobalMethodSecurity|@EnableMethodSecurity|WebSecurityConfigurerAdapter|SecurityFilterChain/i.test(content)) {
|
|
1164
|
+
hasGlobalAuth = true;
|
|
1165
|
+
}
|
|
1166
|
+
// Laravel auth middleware in kernel
|
|
1167
|
+
if (/Kernel\.php|bootstrap\/app/i.test(filePath)) {
|
|
1168
|
+
if (/Authenticate::class|'auth'|auth:sanctum|auth:api/i.test(content)) {
|
|
1169
|
+
hasGlobalAuth = true;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
// Go auth middleware
|
|
1173
|
+
if (/authMiddleware|RequireAuth|JWTMiddleware|AuthRequired/i.test(content) && /\.Use\s*\(|\.With\s*\(/i.test(content)) {
|
|
1174
|
+
hasGlobalAuth = true;
|
|
1175
|
+
}
|
|
1176
|
+
// ASP.NET auth
|
|
1177
|
+
if (/AddAuthentication|UseAuthentication|AddAuthorization|UseAuthorization/i.test(content)) {
|
|
1178
|
+
hasGlobalAuth = true;
|
|
1179
|
+
}
|
|
1180
|
+
// === CORS Detection ===
|
|
1181
|
+
if (/helmet|cors\s*\(\s*\{|CORS_ALLOWED_ORIGINS|@CrossOrigin|AllowedOrigins|WithOrigins/i.test(content)) {
|
|
1182
|
+
hasGlobalCors = true;
|
|
1183
|
+
}
|
|
1184
|
+
// === Security Headers (helmet, etc.) ===
|
|
1185
|
+
if (/import.*helmet|require.*helmet|app\.use\s*\(\s*helmet/i.test(content)) {
|
|
1186
|
+
hasHelmet = true;
|
|
1187
|
+
}
|
|
1188
|
+
if (/SecurityHeadersMiddleware|SECURE_HSTS|X-Content-Type-Options|ContentSecurityPolicy/i.test(content)) {
|
|
1189
|
+
hasHelmet = true;
|
|
1190
|
+
}
|
|
1191
|
+
// === Input Validation Detection ===
|
|
1192
|
+
if (/import.*(?:zod|joi|yup|class-validator|superstruct|valibot|ajv)/i.test(content)) {
|
|
1193
|
+
hasInputValidation = true;
|
|
1194
|
+
}
|
|
1195
|
+
if (/@Valid|@Validated|@RequestBody.*@Valid|ValidationPipe/i.test(content)) {
|
|
1196
|
+
hasInputValidation = true;
|
|
1197
|
+
}
|
|
1198
|
+
// === Reverse Proxy Detection ===
|
|
1199
|
+
if (fileName === "Dockerfile" || fileName === "vercel.json" || fileName === "railway.json" ||
|
|
1200
|
+
fileName === "next.config.ts" || fileName === "next.config.js" || fileName === "next.config.mjs" ||
|
|
1201
|
+
fileName === "nginx.conf" || fileName === "Caddyfile" || fileName === "traefik.yml") {
|
|
1202
|
+
hasReverseProxy = true;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
return { hasMiddlewareRateLimit, hasReverseProxy, hasGlobalAuth, hasGlobalCors, hasHelmet, hasInputValidation };
|
|
1206
|
+
}
|
|
1207
|
+
// Safe sources for dangerouslySetInnerHTML at the file level.
|
|
1208
|
+
// If the file imports/uses any of these, the HTML content is trusted.
|
|
1209
|
+
const SAFE_HTML_SOURCES = [
|
|
1210
|
+
/import.*(?:codeToHtml|shiki|highlight|rehype|remark)/, // Syntax highlighters
|
|
1211
|
+
/import.*DOMPurify/, // Sanitizers
|
|
1212
|
+
/(?:codeToHtml|highlightCode|highlight)\s*\(/, // Highlighter calls
|
|
1213
|
+
/DOMPurify\.sanitize\s*\(/, // Sanitizer calls
|
|
1214
|
+
/JSON\.stringify\s*\(/, // JSON-LD serialization
|
|
1215
|
+
/application\/ld\+json/, // JSON-LD script tags
|
|
1216
|
+
/compileMDX|serialize|bundleMDX|getPostBySlug|\.mdx?$/i, // MDX/Markdown compiled content
|
|
1217
|
+
];
|
|
1218
|
+
/**
|
|
1219
|
+
* File-level dangerouslySetInnerHTML check.
|
|
1220
|
+
* Instead of checking the single line, we check the entire file for
|
|
1221
|
+
* safe content sources (Shiki, DOMPurify, MDX, JSON-LD, etc.).
|
|
1222
|
+
* Only flags dangerouslySetInnerHTML when the file has NO safe source indicators.
|
|
1223
|
+
*/
|
|
1224
|
+
function runDangerousHtmlChecks(files, findings) {
|
|
1225
|
+
for (const [filePath, content] of files) {
|
|
1226
|
+
if (!/\.(?:tsx|jsx)$/.test(filePath))
|
|
1227
|
+
continue;
|
|
1228
|
+
const htmlMatch = /dangerouslySetInnerHTML\s*=\s*\{\s*\{.*__html/.exec(content);
|
|
1229
|
+
if (!htmlMatch)
|
|
1230
|
+
continue;
|
|
1231
|
+
// Check if the file uses any safe HTML source
|
|
1232
|
+
const hasSafeSource = SAFE_HTML_SOURCES.some((pattern) => pattern.test(content));
|
|
1233
|
+
if (hasSafeSource)
|
|
1234
|
+
continue;
|
|
1235
|
+
const lineNumber = getLineNumber(content, htmlMatch.index);
|
|
1236
|
+
const snippet = getSnippet(content, lineNumber);
|
|
1237
|
+
findings.push({
|
|
1238
|
+
vulnerability_id: 46,
|
|
1239
|
+
severity: "high",
|
|
1240
|
+
category: "react-nextjs",
|
|
1241
|
+
title: "dangerouslySetInnerHTML Without Sanitization",
|
|
1242
|
+
file_path: filePath,
|
|
1243
|
+
line_number: lineNumber,
|
|
1244
|
+
code_snippet: snippet,
|
|
1245
|
+
owasp_ref: "A03:2021",
|
|
1246
|
+
status: "open",
|
|
1247
|
+
confidence: "medium",
|
|
1248
|
+
});
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
/**
|
|
1252
|
+
* Absence checks: scan across ALL files to detect when a project is
|
|
1253
|
+
* missing security headers middleware, rate limiting, etc.
|
|
1254
|
+
* These can't be done per-file — we need to look at the whole project.
|
|
1255
|
+
*/
|
|
1256
|
+
function runAbsenceChecks(files, findings) {
|
|
1257
|
+
let hasExpressOrFastify = false;
|
|
1258
|
+
let hasHelmet = false;
|
|
1259
|
+
let hasRateLimiting = false;
|
|
1260
|
+
let hasCors = false;
|
|
1261
|
+
let appFile = "";
|
|
1262
|
+
let appFileLine = 0;
|
|
1263
|
+
for (const [filePath, content] of files) {
|
|
1264
|
+
// Detect the main app/server file (prefer src/ over __tests__/)
|
|
1265
|
+
if (/(?:express\s*\(\)|Fastify\s*\(|new\s+Hono|new\s+Koa)/.test(content)) {
|
|
1266
|
+
hasExpressOrFastify = true;
|
|
1267
|
+
const isTestFile = /(?:__tests__|\.test\.|\.spec\.|test\/|tests\/)/.test(filePath);
|
|
1268
|
+
if (!appFile || (isTestFile === false && /(?:__tests__|\.test\.|\.spec\.)/.test(appFile))) {
|
|
1269
|
+
appFile = filePath;
|
|
1270
|
+
const match = content.match(/(?:express\s*\(\)|Fastify\s*\(|new\s+Hono|new\s+Koa)/);
|
|
1271
|
+
appFileLine = match ? getLineNumber(content, match.index) : 1;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
// Check for security middleware across all files
|
|
1275
|
+
if (/helmet|@fastify\/helmet|secure-headers/i.test(content))
|
|
1276
|
+
hasHelmet = true;
|
|
1277
|
+
if (/rateLimit|rate-limit|@fastify\/rate-limit|express-rate-limit|throttle|RateLimiter/i.test(content))
|
|
1278
|
+
hasRateLimiting = true;
|
|
1279
|
+
if (/cors|@fastify\/cors/i.test(content))
|
|
1280
|
+
hasCors = true;
|
|
1281
|
+
}
|
|
1282
|
+
if (hasExpressOrFastify && !hasHelmet) {
|
|
1283
|
+
findings.push({
|
|
1284
|
+
vulnerability_id: 4,
|
|
1285
|
+
severity: "medium",
|
|
1286
|
+
category: "web-owasp",
|
|
1287
|
+
title: "No Security Headers Middleware (Helmet)",
|
|
1288
|
+
description_technical: "No helmet or security headers middleware detected. This leaves the app without security headers like X-Frame-Options, X-Content-Type-Options, CSP, etc.",
|
|
1289
|
+
file_path: appFile,
|
|
1290
|
+
line_number: appFileLine,
|
|
1291
|
+
code_snippet: "// Add: app.use(helmet()) or fastify.register(helmet)",
|
|
1292
|
+
owasp_ref: "A05:2021",
|
|
1293
|
+
status: "open",
|
|
1294
|
+
confidence: "low",
|
|
1295
|
+
});
|
|
1296
|
+
}
|
|
1297
|
+
if (hasExpressOrFastify && !hasRateLimiting) {
|
|
1298
|
+
findings.push({
|
|
1299
|
+
vulnerability_id: 197,
|
|
1300
|
+
severity: "medium",
|
|
1301
|
+
category: "api",
|
|
1302
|
+
title: "No Rate Limiting Middleware Detected",
|
|
1303
|
+
description_technical: "No rate limiting middleware detected in the project. This makes the API vulnerable to brute-force attacks, DDoS, and abuse.",
|
|
1304
|
+
file_path: appFile,
|
|
1305
|
+
line_number: appFileLine,
|
|
1306
|
+
code_snippet: "// Add: app.use(rateLimit({ ... })) or fastify.register(rateLimitPlugin)",
|
|
1307
|
+
owasp_ref: "A04:2023",
|
|
1308
|
+
status: "open",
|
|
1309
|
+
confidence: "low",
|
|
1310
|
+
});
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function isSourceFile(filePath) {
|
|
1314
|
+
const extensions = [
|
|
1315
|
+
".ts", ".tsx", ".js", ".jsx", ".mjs", ".cjs", ".sql", ".json",
|
|
1316
|
+
".py", ".go", ".java", ".kt", ".php", ".cs", ".rb", ".rs", ".swift",
|
|
1317
|
+
".properties", ".yml", ".yaml",
|
|
1318
|
+
];
|
|
1319
|
+
return extensions.some((ext) => filePath.endsWith(ext));
|
|
1320
|
+
}
|
|
1321
|
+
function getLineNumber(content, charIndex) {
|
|
1322
|
+
return content.substring(0, charIndex).split("\n").length;
|
|
1323
|
+
}
|
|
1324
|
+
function getSnippet(content, lineNumber) {
|
|
1325
|
+
const lines = content.split("\n");
|
|
1326
|
+
const start = Math.max(0, lineNumber - 2);
|
|
1327
|
+
const end = Math.min(lines.length, lineNumber + 2);
|
|
1328
|
+
return lines.slice(start, end).join("\n");
|
|
1329
|
+
}
|
|
1330
|
+
//# sourceMappingURL=patterns.js.map
|