vettcode-cli 1.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/.env.example +20 -0
- package/LICENSE +21 -0
- package/README.md +286 -0
- package/dist/ast-extractor.js +519 -0
- package/dist/cli-scan-orchestrator.js +336 -0
- package/dist/cli.js +208 -0
- package/dist/control-flow-analyzer.js +184 -0
- package/dist/data-flow-analyzer.js +197 -0
- package/dist/enhanced-patterns.js +457 -0
- package/dist/file-collector.js +132 -0
- package/dist/ignore-patterns.js +225 -0
- package/dist/openrouter.js +311 -0
- package/dist/patterns.js +248 -0
- package/dist/prompts.js +144 -0
- package/dist/reference-graph.js +415 -0
- package/dist/report-generator.js +128 -0
- package/dist/scan-priority.js +49 -0
- package/dist/smart-scan-orchestrator.js +878 -0
- package/dist/static-analyzer.js +1681 -0
- package/dist/types.js +2 -0
- package/dist/verification-layer.js +525 -0
- package/package.json +61 -0
|
@@ -0,0 +1,1681 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Static Analysis - Pattern-based vulnerability detection
|
|
4
|
+
* Runs BEFORE AI to catch obvious issues and reduce token usage
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.runEnhancedStaticAnalysis = runEnhancedStaticAnalysis;
|
|
8
|
+
exports.runStaticAnalysis = runStaticAnalysis;
|
|
9
|
+
exports.shouldSendToAI = shouldSendToAI;
|
|
10
|
+
const reference_graph_1 = require("./reference-graph");
|
|
11
|
+
const enhanced_patterns_1 = require("./enhanced-patterns");
|
|
12
|
+
const data_flow_analyzer_1 = require("./data-flow-analyzer");
|
|
13
|
+
const control_flow_analyzer_1 = require("./control-flow-analyzer");
|
|
14
|
+
// Security patterns - these catch 80% of common vulnerabilities
|
|
15
|
+
const SECURITY_PATTERNS = [
|
|
16
|
+
// SQL Injection
|
|
17
|
+
{
|
|
18
|
+
id: "sql-injection-string-concat",
|
|
19
|
+
regex: /(?:execute|query|raw)\s*\(\s*[`"'].*?\$\{|(?:execute|query|raw)\s*\(\s*.*?\+\s*.*?\)/gi,
|
|
20
|
+
severity: "critical",
|
|
21
|
+
category: "security",
|
|
22
|
+
title: "Potential SQL Injection via String Concatenation",
|
|
23
|
+
description: "SQL query uses string concatenation/interpolation instead of parameterized queries",
|
|
24
|
+
confidence: "high",
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
id: "sql-injection-raw-query",
|
|
28
|
+
regex: /\.raw\s*\(\s*[`"'].*?\$\{/gi,
|
|
29
|
+
severity: "critical",
|
|
30
|
+
category: "security",
|
|
31
|
+
title: "Raw SQL Query with Template Literal",
|
|
32
|
+
description: "Using raw SQL with template literals can lead to SQL injection",
|
|
33
|
+
confidence: "high",
|
|
34
|
+
},
|
|
35
|
+
// XSS
|
|
36
|
+
{
|
|
37
|
+
id: "xss-dangerouslysetinnerhtml",
|
|
38
|
+
regex: /dangerouslySetInnerHTML\s*=\s*\{\s*\{?\s*__html\s*:/gi,
|
|
39
|
+
severity: "high",
|
|
40
|
+
category: "security",
|
|
41
|
+
title: "Potential XSS via dangerouslySetInnerHTML",
|
|
42
|
+
description: "Using dangerouslySetInnerHTML without sanitization can lead to XSS",
|
|
43
|
+
confidence: "medium",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
id: "xss-innerhtml",
|
|
47
|
+
regex: /\.innerHTML\s*=(?!.*DOMPurify)/gi,
|
|
48
|
+
severity: "high",
|
|
49
|
+
category: "security",
|
|
50
|
+
title: "Potential XSS via innerHTML",
|
|
51
|
+
description: "Setting innerHTML without sanitization can lead to XSS attacks",
|
|
52
|
+
confidence: "medium",
|
|
53
|
+
},
|
|
54
|
+
// Secrets in Code
|
|
55
|
+
{
|
|
56
|
+
id: "hardcoded-secret-api-key",
|
|
57
|
+
regex: /(?:api[_-]?key|apikey|api[_-]?secret)\s*[=:]\s*[`"'](?!process\.env|YOUR_|XXX|sk-or-v1-your)[a-zA-Z0-9_\-]{20,}[`"']/gi,
|
|
58
|
+
severity: "critical",
|
|
59
|
+
category: "security",
|
|
60
|
+
title: "Hardcoded API Key Detected",
|
|
61
|
+
description: "API key is hardcoded in source code instead of using environment variables",
|
|
62
|
+
confidence: "high",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
id: "hardcoded-password",
|
|
66
|
+
regex: /(?:password|passwd|pwd)\s*[=:]\s*[`"'](?!process\.env|YOUR_|\*+|password|admin|test)[^`"'\s]{6,}[`"']/gi,
|
|
67
|
+
severity: "critical",
|
|
68
|
+
category: "security",
|
|
69
|
+
title: "Hardcoded Password Detected",
|
|
70
|
+
description: "Password is hardcoded in source code",
|
|
71
|
+
confidence: "medium",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: "hardcoded-jwt-secret",
|
|
75
|
+
regex: /(?:jwt[_-]?secret|secret[_-]?key)\s*[=:]\s*[`"'](?!process\.env|YOUR_)[a-zA-Z0-9_\-]{16,}[`"']/gi,
|
|
76
|
+
severity: "critical",
|
|
77
|
+
category: "security",
|
|
78
|
+
title: "Hardcoded JWT Secret",
|
|
79
|
+
description: "JWT secret is hardcoded instead of using environment variables",
|
|
80
|
+
confidence: "high",
|
|
81
|
+
},
|
|
82
|
+
// Auth Issues
|
|
83
|
+
{
|
|
84
|
+
id: "weak-jwt-algorithm",
|
|
85
|
+
regex: /algorithm\s*:\s*[`"'](?:none|HS256)[`"']/gi,
|
|
86
|
+
severity: "high",
|
|
87
|
+
category: "security",
|
|
88
|
+
title: "Weak JWT Algorithm",
|
|
89
|
+
description: "Using 'none' or weak algorithm for JWT signing",
|
|
90
|
+
confidence: "high",
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: "missing-auth-check",
|
|
94
|
+
regex: /(?:router\.(?:post|put|delete|patch)|app\.(?:post|put|delete|patch))\s*\([^)]*\)\s*(?:,\s*)?(?:async\s*)?\([^)]*\)\s*(?:=>)?\s*\{(?![\s\S]{0,200}(?:auth|verify|check|middleware|protect|guard))/gi,
|
|
95
|
+
severity: "high",
|
|
96
|
+
category: "security",
|
|
97
|
+
title: "Potential Missing Authentication Check",
|
|
98
|
+
description: "Mutating endpoint may lack authentication middleware",
|
|
99
|
+
confidence: "low",
|
|
100
|
+
},
|
|
101
|
+
// CORS Issues
|
|
102
|
+
{
|
|
103
|
+
id: "open-cors",
|
|
104
|
+
regex: /Access-Control-Allow-Origin[`"']\s*[,:]?\s*[`"']\*/gi,
|
|
105
|
+
severity: "medium",
|
|
106
|
+
category: "security",
|
|
107
|
+
title: "Open CORS Policy",
|
|
108
|
+
description: "CORS allows all origins (*) which may expose sensitive endpoints",
|
|
109
|
+
confidence: "high",
|
|
110
|
+
},
|
|
111
|
+
// Crypto Issues
|
|
112
|
+
{
|
|
113
|
+
id: "weak-crypto-md5",
|
|
114
|
+
regex: /(?:createHash|crypto\.createHash)\s*\(\s*[`"'](?:md5|sha1)[`"']/gi,
|
|
115
|
+
severity: "medium",
|
|
116
|
+
category: "security",
|
|
117
|
+
title: "Weak Cryptographic Hash",
|
|
118
|
+
description: "Using MD5 or SHA1 which are cryptographically broken",
|
|
119
|
+
confidence: "high",
|
|
120
|
+
},
|
|
121
|
+
// Command Injection
|
|
122
|
+
{
|
|
123
|
+
id: "command-injection-exec",
|
|
124
|
+
regex: /(?:exec|spawn|execSync|spawnSync)\s*\(\s*[`"'].*?\$\{|(?:exec|spawn)\s*\(.*?\+/gi,
|
|
125
|
+
severity: "critical",
|
|
126
|
+
category: "security",
|
|
127
|
+
title: "Potential Command Injection",
|
|
128
|
+
description: "Executing shell commands with user input can lead to command injection",
|
|
129
|
+
confidence: "medium",
|
|
130
|
+
},
|
|
131
|
+
// Path Traversal
|
|
132
|
+
{
|
|
133
|
+
id: "path-traversal",
|
|
134
|
+
regex: /(?:readFile|writeFile|unlink|rmdir|mkdir)\s*\([^)]*(?:\+|`\$\{)(?!.*(?:path\.join|path\.resolve|sanitize))/gi,
|
|
135
|
+
severity: "high",
|
|
136
|
+
category: "security",
|
|
137
|
+
title: "Potential Path Traversal",
|
|
138
|
+
description: "File operations with unsanitized paths can lead to path traversal attacks",
|
|
139
|
+
confidence: "low",
|
|
140
|
+
},
|
|
141
|
+
// Production Issues
|
|
142
|
+
// DISABLED: This pattern is fundamentally flawed - it flags function declarations instead of actual unhandled promises
|
|
143
|
+
// {
|
|
144
|
+
// id: "unhandled-promise",
|
|
145
|
+
// regex: /(?:async\s+function|async\s*\(|Promise\.(?:all|race))\s*[^{]*\{[^}]*(?:await\s+[^;]+;?)(?![^}]*\.catch\(|[^}]*try\s*\{)/gi,
|
|
146
|
+
// severity: "medium",
|
|
147
|
+
// category: "production",
|
|
148
|
+
// title: "Unhandled Promise Rejection",
|
|
149
|
+
// description: "Async operation without error handling can crash the application",
|
|
150
|
+
// confidence: "low",
|
|
151
|
+
// },
|
|
152
|
+
{
|
|
153
|
+
id: "console-log-production",
|
|
154
|
+
regex: /console\.(?:log|debug|info)\(/gi,
|
|
155
|
+
severity: "low",
|
|
156
|
+
category: "production",
|
|
157
|
+
title: "Console Statement in Production Code",
|
|
158
|
+
description: "Console statements should be removed or replaced with proper logging",
|
|
159
|
+
confidence: "high",
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: "eval-usage",
|
|
163
|
+
regex: /\beval\s*\(/gi,
|
|
164
|
+
severity: "critical",
|
|
165
|
+
category: "security",
|
|
166
|
+
title: "Use of eval()",
|
|
167
|
+
description: "eval() can execute arbitrary code and is a major security risk",
|
|
168
|
+
confidence: "high",
|
|
169
|
+
},
|
|
170
|
+
// Database Issues
|
|
171
|
+
{
|
|
172
|
+
id: "missing-db-transaction",
|
|
173
|
+
regex: /(?:INSERT|UPDATE|DELETE).*?(?:INSERT|UPDATE|DELETE)(?![\s\S]{0,300}(?:transaction|BEGIN|COMMIT))/gi,
|
|
174
|
+
severity: "medium",
|
|
175
|
+
category: "database",
|
|
176
|
+
title: "Multiple DB Operations Without Transaction",
|
|
177
|
+
description: "Multiple database mutations should be wrapped in a transaction",
|
|
178
|
+
confidence: "low",
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: "n-plus-one-query",
|
|
182
|
+
regex: /\.map\s*\([^)]*(?:await|\.then)\s*\([^)]*(?:find|query|get)/gi,
|
|
183
|
+
severity: "high",
|
|
184
|
+
category: "performance",
|
|
185
|
+
title: "Potential N+1 Query Problem",
|
|
186
|
+
description: "Querying database inside a loop/map can cause severe performance issues",
|
|
187
|
+
confidence: "medium",
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
id: "missing-db-index-hint",
|
|
191
|
+
regex: /WHERE\s+\w+\s*=(?![\s\S]{0,100}INDEX)/gi,
|
|
192
|
+
severity: "low",
|
|
193
|
+
category: "performance",
|
|
194
|
+
title: "Query May Need Index",
|
|
195
|
+
description: "Frequent WHERE clauses should have corresponding database indexes",
|
|
196
|
+
confidence: "low",
|
|
197
|
+
},
|
|
198
|
+
// Code Quality & Best Practices
|
|
199
|
+
{
|
|
200
|
+
id: "magic-numbers",
|
|
201
|
+
regex: /(?:if|while|for|return|===|!==|>|<|>=|<=)\s*\(?[^)]*\b(?!0|1|100|200|201|204|400|401|403|404|500)\d{3,}\b/gi,
|
|
202
|
+
severity: "low",
|
|
203
|
+
category: "code-quality",
|
|
204
|
+
title: "Magic Number Detected",
|
|
205
|
+
description: "Unexplained numeric literals should be named constants",
|
|
206
|
+
confidence: "low",
|
|
207
|
+
},
|
|
208
|
+
{
|
|
209
|
+
id: "long-function",
|
|
210
|
+
regex: /(?:function|=>)\s*[^{]*\{[\s\S]{2000,}?\n\}/gm,
|
|
211
|
+
severity: "low",
|
|
212
|
+
category: "code-quality",
|
|
213
|
+
title: "Function Too Long",
|
|
214
|
+
description: "Function exceeds 100+ lines, consider breaking into smaller functions",
|
|
215
|
+
confidence: "medium",
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
id: "deep-nesting",
|
|
219
|
+
regex: /\{\s*\n\s+if\s*\([^)]*\)\s*\{\s*\n\s+if\s*\([^)]*\)\s*\{\s*\n\s+if\s*\([^)]*\)\s*\{/gi,
|
|
220
|
+
severity: "low",
|
|
221
|
+
category: "code-quality",
|
|
222
|
+
title: "Deep Nesting Detected",
|
|
223
|
+
description: "More than 3 levels of nesting makes code hard to read and maintain",
|
|
224
|
+
confidence: "high",
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: "commented-code",
|
|
228
|
+
regex: /\/\/\s*(?:function|const|let|var|if|for|while|class)\s+\w+/gi,
|
|
229
|
+
severity: "info",
|
|
230
|
+
category: "code-quality",
|
|
231
|
+
title: "Commented-Out Code",
|
|
232
|
+
description: "Commented code should be removed (use version control instead)",
|
|
233
|
+
confidence: "high",
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
id: "todo-fixme",
|
|
237
|
+
regex: /\/\/\s*(?:TODO|FIXME|HACK|XXX|BUG):/gi,
|
|
238
|
+
severity: "info",
|
|
239
|
+
category: "code-quality",
|
|
240
|
+
title: "TODO/FIXME Comment",
|
|
241
|
+
description: "Unresolved TODO or FIXME comment indicates incomplete work",
|
|
242
|
+
confidence: "high",
|
|
243
|
+
},
|
|
244
|
+
{
|
|
245
|
+
id: "var-usage",
|
|
246
|
+
regex: /\bvar\s+\w+/g,
|
|
247
|
+
severity: "low",
|
|
248
|
+
category: "code-quality",
|
|
249
|
+
title: "Use of 'var' Instead of 'let'/'const'",
|
|
250
|
+
description: "var has function scope and can cause bugs, use let or const instead",
|
|
251
|
+
confidence: "high",
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
id: "any-type-typescript",
|
|
255
|
+
regex: /:\s*any\b/g,
|
|
256
|
+
severity: "low",
|
|
257
|
+
category: "typing",
|
|
258
|
+
title: "TypeScript 'any' Type Usage",
|
|
259
|
+
description: "Using 'any' defeats the purpose of TypeScript, use proper types",
|
|
260
|
+
confidence: "high",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
id: "missing-return-type",
|
|
264
|
+
regex: /(?:export\s+)?(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*\{(?![\s\S]{0,50}:\s*\w+)/gi,
|
|
265
|
+
severity: "low",
|
|
266
|
+
category: "typing",
|
|
267
|
+
title: "Missing Return Type Annotation",
|
|
268
|
+
description: "Functions should have explicit return type annotations in TypeScript",
|
|
269
|
+
confidence: "low",
|
|
270
|
+
},
|
|
271
|
+
// Error Handling
|
|
272
|
+
{
|
|
273
|
+
id: "empty-catch",
|
|
274
|
+
regex: /catch\s*\([^)]*\)\s*\{\s*\}/gi,
|
|
275
|
+
severity: "high",
|
|
276
|
+
category: "production",
|
|
277
|
+
title: "Empty Catch Block",
|
|
278
|
+
description: "Silently swallowing errors makes debugging impossible",
|
|
279
|
+
confidence: "high",
|
|
280
|
+
},
|
|
281
|
+
{
|
|
282
|
+
id: "generic-error-message",
|
|
283
|
+
regex: /throw\s+new\s+Error\s*\(\s*[`"'](?:error|failed|invalid)[`"']\s*\)/gi,
|
|
284
|
+
severity: "low",
|
|
285
|
+
category: "production",
|
|
286
|
+
title: "Generic Error Message",
|
|
287
|
+
description: "Error messages should be specific and actionable",
|
|
288
|
+
confidence: "medium",
|
|
289
|
+
},
|
|
290
|
+
{
|
|
291
|
+
id: "missing-finally",
|
|
292
|
+
regex: /try\s*\{[\s\S]*?\}\s*catch\s*\([^)]*\)\s*\{[\s\S]*?\}(?!\s*finally)/gi,
|
|
293
|
+
severity: "low",
|
|
294
|
+
category: "production",
|
|
295
|
+
title: "Try-Catch Without Finally",
|
|
296
|
+
description: "Consider using finally block for cleanup operations",
|
|
297
|
+
confidence: "low",
|
|
298
|
+
},
|
|
299
|
+
// Async/Await Issues
|
|
300
|
+
{
|
|
301
|
+
id: "floating-promise",
|
|
302
|
+
regex: /^\s*(?!await|return|const|let|var)\w+\([^)]*\)\.then\(/gm,
|
|
303
|
+
severity: "medium",
|
|
304
|
+
category: "production",
|
|
305
|
+
title: "Floating Promise",
|
|
306
|
+
description: "Promise not awaited or assigned, errors will be unhandled",
|
|
307
|
+
confidence: "medium",
|
|
308
|
+
},
|
|
309
|
+
{
|
|
310
|
+
id: "async-without-await",
|
|
311
|
+
regex: /async\s+(?:function|\([^)]*\)\s*=>)\s*[^{]*\{(?![\s\S]*await)[\s\S]{0,500}\}/gi,
|
|
312
|
+
severity: "low",
|
|
313
|
+
category: "code-quality",
|
|
314
|
+
title: "Async Function Without Await",
|
|
315
|
+
description: "Function marked async but doesn't use await",
|
|
316
|
+
confidence: "low",
|
|
317
|
+
},
|
|
318
|
+
{
|
|
319
|
+
id: "promise-constructor-antipattern",
|
|
320
|
+
regex: /new\s+Promise\s*\([^)]*\)\s*\{[\s\S]*?(?:async|await)/gi,
|
|
321
|
+
severity: "medium",
|
|
322
|
+
category: "code-quality",
|
|
323
|
+
title: "Promise Constructor Anti-pattern",
|
|
324
|
+
description: "Wrapping async functions in Promise constructor is redundant",
|
|
325
|
+
confidence: "medium",
|
|
326
|
+
},
|
|
327
|
+
// React-Specific Issues
|
|
328
|
+
{
|
|
329
|
+
id: "missing-key-prop",
|
|
330
|
+
regex: /\.map\s*\([^)]*\)\s*(?:=>)?\s*<(?![\s\S]{0,100}key=)/gi,
|
|
331
|
+
severity: "medium",
|
|
332
|
+
category: "react",
|
|
333
|
+
title: "Missing Key Prop in List",
|
|
334
|
+
description: "React list items should have unique key prop for performance",
|
|
335
|
+
confidence: "medium",
|
|
336
|
+
},
|
|
337
|
+
{
|
|
338
|
+
id: "useeffect-missing-deps",
|
|
339
|
+
regex: /useEffect\s*\([^,]*,\s*\[\s*\]\s*\)/gi,
|
|
340
|
+
severity: "low",
|
|
341
|
+
category: "react",
|
|
342
|
+
title: "useEffect with Empty Dependency Array",
|
|
343
|
+
description: "Empty deps array may indicate missing dependencies",
|
|
344
|
+
confidence: "low",
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
id: "inline-function-prop",
|
|
348
|
+
regex: /(?:onClick|onChange|onSubmit|onBlur|onFocus)\s*=\s*\{(?:\([^)]*\)\s*=>|\s*function)/gi,
|
|
349
|
+
severity: "low",
|
|
350
|
+
category: "performance",
|
|
351
|
+
title: "Inline Function in JSX Prop",
|
|
352
|
+
description: "Inline functions cause unnecessary re-renders, define outside render",
|
|
353
|
+
confidence: "low",
|
|
354
|
+
},
|
|
355
|
+
// API & Network Issues
|
|
356
|
+
{
|
|
357
|
+
id: "missing-timeout",
|
|
358
|
+
regex: /(?:fetch|axios|http\.request)\s*\((?![\s\S]{0,200}timeout)/gi,
|
|
359
|
+
severity: "medium",
|
|
360
|
+
category: "production",
|
|
361
|
+
title: "Network Request Without Timeout",
|
|
362
|
+
description: "Network requests should have timeouts to prevent hanging",
|
|
363
|
+
confidence: "medium",
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
id: "missing-retry-logic",
|
|
367
|
+
regex: /fetch\s*\([^)]*\)(?![\s\S]{0,300}(?:retry|catch))/gi,
|
|
368
|
+
severity: "low",
|
|
369
|
+
category: "reliability",
|
|
370
|
+
title: "No Retry Logic for Network Request",
|
|
371
|
+
description: "Critical network requests should have retry logic",
|
|
372
|
+
confidence: "low",
|
|
373
|
+
},
|
|
374
|
+
{
|
|
375
|
+
id: "http-not-https",
|
|
376
|
+
regex: /['"]http:\/\/(?!localhost|127\.0\.0\.1)/gi,
|
|
377
|
+
severity: "medium",
|
|
378
|
+
category: "security",
|
|
379
|
+
title: "HTTP Instead of HTTPS",
|
|
380
|
+
description: "Using HTTP instead of HTTPS exposes data to interception",
|
|
381
|
+
confidence: "high",
|
|
382
|
+
},
|
|
383
|
+
// Environment & Configuration
|
|
384
|
+
{
|
|
385
|
+
id: "missing-env-check",
|
|
386
|
+
regex: /process\.env\.(\w+)(?![\s\S]{0,50}(?:\|\||&&|\?|throw|if))/gi,
|
|
387
|
+
severity: "medium",
|
|
388
|
+
category: "production",
|
|
389
|
+
title: "Environment Variable Without Validation",
|
|
390
|
+
description: "Environment variables should be validated before use",
|
|
391
|
+
confidence: "low",
|
|
392
|
+
},
|
|
393
|
+
{
|
|
394
|
+
id: "debug-mode-production",
|
|
395
|
+
regex: /(?:DEBUG|VERBOSE|LOG_LEVEL)\s*[=:]\s*['"](?:true|debug|verbose|all)['"]/gi,
|
|
396
|
+
severity: "medium",
|
|
397
|
+
category: "configuration",
|
|
398
|
+
title: "Debug Mode Enabled",
|
|
399
|
+
description: "Debug mode should be disabled in production",
|
|
400
|
+
confidence: "medium",
|
|
401
|
+
},
|
|
402
|
+
// Memory & Resource Leaks
|
|
403
|
+
{
|
|
404
|
+
id: "missing-cleanup",
|
|
405
|
+
regex: /(?:setInterval|setTimeout|addEventListener)(?![\s\S]{0,500}(?:clearInterval|clearTimeout|removeEventListener|return\s*\(\s*\)\s*=>))/gi,
|
|
406
|
+
severity: "medium",
|
|
407
|
+
category: "production",
|
|
408
|
+
title: "Potential Memory Leak",
|
|
409
|
+
description: "Timers and event listeners should be cleaned up",
|
|
410
|
+
confidence: "low",
|
|
411
|
+
},
|
|
412
|
+
{
|
|
413
|
+
id: "large-array-operation",
|
|
414
|
+
regex: /\.(?:map|filter|reduce)\s*\([^)]*\)\.(?:map|filter|reduce)\s*\([^)]*\)\.(?:map|filter|reduce)/gi,
|
|
415
|
+
severity: "low",
|
|
416
|
+
category: "performance",
|
|
417
|
+
title: "Chained Array Operations",
|
|
418
|
+
description: "Multiple chained array operations can be optimized into single pass",
|
|
419
|
+
confidence: "medium",
|
|
420
|
+
},
|
|
421
|
+
// Race Conditions
|
|
422
|
+
{
|
|
423
|
+
id: "race-condition-state",
|
|
424
|
+
regex: /setState\s*\([^)]*\)[\s\S]{0,100}setState\s*\(/gi,
|
|
425
|
+
severity: "medium",
|
|
426
|
+
category: "logic",
|
|
427
|
+
title: "Potential Race Condition in State Updates",
|
|
428
|
+
description: "Multiple setState calls can cause race conditions, use functional updates",
|
|
429
|
+
confidence: "low",
|
|
430
|
+
},
|
|
431
|
+
// AI-Generated Code Issues
|
|
432
|
+
{
|
|
433
|
+
id: "ai-placeholder-todo",
|
|
434
|
+
regex: /\/\/\s*(?:TODO|FIXME|IMPLEMENT|PLACEHOLDER|AI_GENERATED|COPILOT|CHATGPT).*?(?:implement|add|fix|complete|here)/gi,
|
|
435
|
+
severity: "high",
|
|
436
|
+
category: "production",
|
|
437
|
+
title: "AI-Generated Placeholder Code",
|
|
438
|
+
description: "Code contains AI-generated placeholders that need implementation",
|
|
439
|
+
confidence: "high",
|
|
440
|
+
},
|
|
441
|
+
{
|
|
442
|
+
id: "ai-mock-data",
|
|
443
|
+
regex: /(?:const|let|var)\s+\w+\s*=\s*(?:\[|\{)[\s\S]{0,200}(?:example|sample|mock|dummy|test|placeholder|fake)[\s\S]{0,200}(?:\]|\})/gi,
|
|
444
|
+
severity: "medium",
|
|
445
|
+
category: "production",
|
|
446
|
+
title: "Mock/Placeholder Data in Production Code",
|
|
447
|
+
description: "Code contains mock or example data that should be replaced with real data",
|
|
448
|
+
confidence: "medium",
|
|
449
|
+
},
|
|
450
|
+
{
|
|
451
|
+
id: "ai-generic-error",
|
|
452
|
+
regex: /catch\s*\([^)]*\)\s*\{[\s\S]{0,100}(?:console\.log|alert)\s*\(\s*[`"'](?:error|oops|something went wrong|an error occurred)[`"']/gi,
|
|
453
|
+
severity: "medium",
|
|
454
|
+
category: "production",
|
|
455
|
+
title: "Generic AI-Generated Error Handling",
|
|
456
|
+
description: "Error handling is too generic and doesn't provide actionable information",
|
|
457
|
+
confidence: "medium",
|
|
458
|
+
},
|
|
459
|
+
// Database Performance & Scalability Issues
|
|
460
|
+
{
|
|
461
|
+
id: "db-query-in-loop",
|
|
462
|
+
regex: /(?:for|while|forEach|map)\s*\([^)]*\)\s*(?:=>)?\s*\{[\s\S]{0,300}(?:query|execute|find|findOne|findMany|create|update|delete)\s*\(/gi,
|
|
463
|
+
severity: "critical",
|
|
464
|
+
category: "performance",
|
|
465
|
+
title: "Database Query Inside Loop (N+1 Problem)",
|
|
466
|
+
description: "Executing database queries in a loop will cause severe performance issues under load. Use batch queries or eager loading.",
|
|
467
|
+
confidence: "high",
|
|
468
|
+
},
|
|
469
|
+
{
|
|
470
|
+
id: "db-select-all",
|
|
471
|
+
regex: /(?:SELECT\s+\*|find\(\s*\{?\s*\}?\s*\)|findMany\(\s*\))/gi,
|
|
472
|
+
severity: "high",
|
|
473
|
+
category: "performance",
|
|
474
|
+
title: "SELECT * or Fetch All Records",
|
|
475
|
+
description: "Fetching all columns or all records without pagination will break under high load",
|
|
476
|
+
confidence: "medium",
|
|
477
|
+
},
|
|
478
|
+
{
|
|
479
|
+
id: "db-missing-limit",
|
|
480
|
+
regex: /(?:find|findMany|query|execute)\s*\([^)]{0,200}\)(?![\s\S]{0,100}(?:limit|take|top|first|slice))/gi,
|
|
481
|
+
severity: "high",
|
|
482
|
+
category: "performance",
|
|
483
|
+
title: "Database Query Without Limit",
|
|
484
|
+
description: "Query without LIMIT/pagination can return millions of rows and crash the application",
|
|
485
|
+
confidence: "low",
|
|
486
|
+
},
|
|
487
|
+
{
|
|
488
|
+
id: "db-no-connection-pool",
|
|
489
|
+
regex: /new\s+(?:Client|Connection|Database)\s*\((?![\s\S]{0,200}pool)/gi,
|
|
490
|
+
severity: "high",
|
|
491
|
+
category: "database",
|
|
492
|
+
title: "Database Connection Without Pooling",
|
|
493
|
+
description: "Creating new connections without pooling will exhaust database connections under load",
|
|
494
|
+
confidence: "medium",
|
|
495
|
+
},
|
|
496
|
+
{
|
|
497
|
+
id: "db-synchronous-operation",
|
|
498
|
+
regex: /(?:executeSync|querySync|readFileSync|writeFileSync)\s*\(/gi,
|
|
499
|
+
severity: "critical",
|
|
500
|
+
category: "performance",
|
|
501
|
+
title: "Synchronous Database/File Operation",
|
|
502
|
+
description: "Synchronous operations block the event loop and will freeze the application under load",
|
|
503
|
+
confidence: "high",
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
id: "db-missing-index-hint",
|
|
507
|
+
regex: /WHERE\s+\w+\s*(?:=|>|<|>=|<=|LIKE)(?![\s\S]{0,200}(?:INDEX|INDEXED|USE INDEX))/gi,
|
|
508
|
+
severity: "medium",
|
|
509
|
+
category: "performance",
|
|
510
|
+
title: "Query May Need Database Index",
|
|
511
|
+
description: "Frequent WHERE clauses without indexes will cause slow queries under high traffic",
|
|
512
|
+
confidence: "low",
|
|
513
|
+
},
|
|
514
|
+
{
|
|
515
|
+
id: "db-cascade-delete-risk",
|
|
516
|
+
regex: /ON\s+DELETE\s+CASCADE|cascade\s*:\s*true/gi,
|
|
517
|
+
severity: "high",
|
|
518
|
+
category: "database",
|
|
519
|
+
title: "Cascade Delete Risk",
|
|
520
|
+
description: "CASCADE DELETE can accidentally delete large amounts of data. Use soft deletes or explicit deletion.",
|
|
521
|
+
confidence: "high",
|
|
522
|
+
},
|
|
523
|
+
// Memory Leaks & Resource Exhaustion
|
|
524
|
+
{
|
|
525
|
+
id: "memory-leak-global-array",
|
|
526
|
+
regex: /(?:const|let|var)\s+\w+\s*=\s*\[\][\s\S]{0,500}\.push\(/gi,
|
|
527
|
+
severity: "high",
|
|
528
|
+
category: "production",
|
|
529
|
+
title: "Potential Memory Leak - Unbounded Array Growth",
|
|
530
|
+
description: "Global array that grows indefinitely will cause memory leaks under sustained load",
|
|
531
|
+
confidence: "low",
|
|
532
|
+
},
|
|
533
|
+
{
|
|
534
|
+
id: "memory-leak-cache-no-limit",
|
|
535
|
+
regex: /(?:cache|store|map)\s*=\s*new\s+Map\(\)(?![\s\S]{0,300}(?:maxSize|limit|evict|clear))/gi,
|
|
536
|
+
severity: "high",
|
|
537
|
+
category: "production",
|
|
538
|
+
title: "Cache Without Size Limit",
|
|
539
|
+
description: "Unbounded cache will grow indefinitely and cause out-of-memory errors",
|
|
540
|
+
confidence: "medium",
|
|
541
|
+
},
|
|
542
|
+
{
|
|
543
|
+
id: "file-upload-no-size-limit",
|
|
544
|
+
regex: /(?:upload|multer|formidable)(?![\s\S]{0,200}(?:limits|maxSize|maxFileSize))/gi,
|
|
545
|
+
severity: "critical",
|
|
546
|
+
category: "security",
|
|
547
|
+
title: "File Upload Without Size Limit",
|
|
548
|
+
description: "File uploads without size limits can be used for DoS attacks",
|
|
549
|
+
confidence: "medium",
|
|
550
|
+
},
|
|
551
|
+
// Concurrency & Rate Limiting Issues
|
|
552
|
+
{
|
|
553
|
+
id: "missing-rate-limit",
|
|
554
|
+
regex: /(?:router\.post|app\.post|router\.put|app\.put)\s*\([^)]*\)(?![\s\S]{0,300}(?:rateLimit|limiter|throttle))/gi,
|
|
555
|
+
severity: "high",
|
|
556
|
+
category: "security",
|
|
557
|
+
title: "API Endpoint Without Rate Limiting",
|
|
558
|
+
description: "Endpoints without rate limiting are vulnerable to abuse and DoS attacks",
|
|
559
|
+
confidence: "low",
|
|
560
|
+
},
|
|
561
|
+
{
|
|
562
|
+
id: "missing-request-timeout",
|
|
563
|
+
regex: /(?:fetch|axios|request|http\.get|https\.get)\s*\((?![\s\S]{0,200}timeout)/gi,
|
|
564
|
+
severity: "medium",
|
|
565
|
+
category: "production",
|
|
566
|
+
title: "HTTP Request Without Timeout",
|
|
567
|
+
description: "Requests without timeouts can hang indefinitely and exhaust resources",
|
|
568
|
+
confidence: "medium",
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
id: "promise-all-no-error-handling",
|
|
572
|
+
regex: /Promise\.all\s*\([^)]*\)(?![\s\S]{0,100}\.catch)/gi,
|
|
573
|
+
severity: "high",
|
|
574
|
+
category: "production",
|
|
575
|
+
title: "Promise.all Without Error Handling",
|
|
576
|
+
description: "Promise.all fails if any promise rejects. Use Promise.allSettled for resilience.",
|
|
577
|
+
confidence: "medium",
|
|
578
|
+
},
|
|
579
|
+
// Authentication & Authorization Issues
|
|
580
|
+
{
|
|
581
|
+
id: "weak-password-validation",
|
|
582
|
+
regex: /password.*?\.length\s*[<>=]+\s*[1-7]\b/gi,
|
|
583
|
+
severity: "high",
|
|
584
|
+
category: "security",
|
|
585
|
+
title: "Weak Password Length Requirement",
|
|
586
|
+
description: "Password minimum length is too short (< 8 characters)",
|
|
587
|
+
confidence: "high",
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
id: "missing-csrf-protection",
|
|
591
|
+
regex: /(?:router\.post|app\.post|router\.put|app\.put|router\.delete|app\.delete)(?![\s\S]{0,300}(?:csrf|csurf|csrfToken))/gi,
|
|
592
|
+
severity: "high",
|
|
593
|
+
category: "security",
|
|
594
|
+
title: "Missing CSRF Protection",
|
|
595
|
+
description: "State-changing endpoints should have CSRF protection",
|
|
596
|
+
confidence: "low",
|
|
597
|
+
},
|
|
598
|
+
{
|
|
599
|
+
id: "session-no-expiry",
|
|
600
|
+
regex: /session\s*\((?![\s\S]{0,200}(?:maxAge|expires|cookie))/gi,
|
|
601
|
+
severity: "medium",
|
|
602
|
+
category: "security",
|
|
603
|
+
title: "Session Without Expiration",
|
|
604
|
+
description: "Sessions without expiration can be hijacked indefinitely",
|
|
605
|
+
confidence: "medium",
|
|
606
|
+
},
|
|
607
|
+
// API Design Issues
|
|
608
|
+
{
|
|
609
|
+
id: "api-no-pagination",
|
|
610
|
+
regex: /(?:router\.get|app\.get)\s*\([^)]*\)[\s\S]{0,500}(?:findMany|find\(\s*\{?\s*\}?\s*\)|SELECT\s+\*)(?![\s\S]{0,200}(?:limit|take|skip|offset|page))/gi,
|
|
611
|
+
severity: "critical",
|
|
612
|
+
category: "performance",
|
|
613
|
+
title: "API Endpoint Returns All Records Without Pagination",
|
|
614
|
+
description: "Returning all records will cause timeouts and memory issues with large datasets",
|
|
615
|
+
confidence: "low",
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
id: "api-no-input-validation",
|
|
619
|
+
regex: /(?:req\.body|req\.query|req\.params)\.(\w+)(?![\s\S]{0,100}(?:validate|check|sanitize|schema|zod|joi))/gi,
|
|
620
|
+
severity: "high",
|
|
621
|
+
category: "security",
|
|
622
|
+
title: "API Input Without Validation",
|
|
623
|
+
description: "User input should be validated before processing",
|
|
624
|
+
confidence: "low",
|
|
625
|
+
},
|
|
626
|
+
// Logging & Monitoring Issues
|
|
627
|
+
{
|
|
628
|
+
id: "logging-sensitive-data",
|
|
629
|
+
regex: /console\.log\([^)]*(?:password|token|secret|key|credential|ssn|credit)/gi,
|
|
630
|
+
severity: "critical",
|
|
631
|
+
category: "security",
|
|
632
|
+
title: "Logging Sensitive Data",
|
|
633
|
+
description: "Sensitive data should never be logged",
|
|
634
|
+
confidence: "high",
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: "no-error-tracking",
|
|
638
|
+
regex: /catch\s*\([^)]*\)\s*\{[\s\S]{0,200}\}(?![\s\S]{0,100}(?:sentry|bugsnag|rollbar|logger|log))/gi,
|
|
639
|
+
severity: "medium",
|
|
640
|
+
category: "production",
|
|
641
|
+
title: "Error Not Tracked or Logged",
|
|
642
|
+
description: "Errors should be logged to monitoring systems for debugging",
|
|
643
|
+
confidence: "low",
|
|
644
|
+
},
|
|
645
|
+
];
|
|
646
|
+
/**
|
|
647
|
+
* Run ENHANCED static analysis with all advanced features
|
|
648
|
+
* This is used when AI fails - provides 85% coverage vs 60% with basic patterns
|
|
649
|
+
*/
|
|
650
|
+
function runEnhancedStaticAnalysis(files) {
|
|
651
|
+
const startTime = Date.now();
|
|
652
|
+
// 1. Build reference graph
|
|
653
|
+
const referenceGraph = (0, reference_graph_1.buildReferenceGraph)(files);
|
|
654
|
+
// 2. Run pattern-based analysis (original + enhanced patterns)
|
|
655
|
+
const allPatterns = [...SECURITY_PATTERNS, ...enhanced_patterns_1.ALL_ENHANCED_PATTERNS];
|
|
656
|
+
const patternFindings = runPatternsWithGraph(files, allPatterns, referenceGraph);
|
|
657
|
+
// 3. Run data flow analysis
|
|
658
|
+
const dataFlowFindings = (0, data_flow_analyzer_1.analyzeDataFlow)(files);
|
|
659
|
+
// 4. Run control flow analysis
|
|
660
|
+
const controlFlowFindings = (0, control_flow_analyzer_1.analyzeControlFlow)(files);
|
|
661
|
+
// 5. Merge all findings
|
|
662
|
+
const allFindings = [
|
|
663
|
+
...patternFindings,
|
|
664
|
+
...convertDataFlowFindings(dataFlowFindings),
|
|
665
|
+
...convertControlFlowFindings(controlFlowFindings),
|
|
666
|
+
];
|
|
667
|
+
// 6. Deduplicate
|
|
668
|
+
const uniqueFindings = deduplicateFindings(allFindings);
|
|
669
|
+
const totalTime = Date.now() - startTime;
|
|
670
|
+
return {
|
|
671
|
+
findings: uniqueFindings,
|
|
672
|
+
quality: {
|
|
673
|
+
level: 'enhanced',
|
|
674
|
+
patternsUsed: allPatterns.length,
|
|
675
|
+
dataFlowAnalysis: true,
|
|
676
|
+
controlFlowAnalysis: true,
|
|
677
|
+
referenceGraph: true,
|
|
678
|
+
confidence: 85, // 85% coverage without AI
|
|
679
|
+
},
|
|
680
|
+
stats: {
|
|
681
|
+
totalPatterns: allPatterns.length,
|
|
682
|
+
filesAnalyzed: files.length,
|
|
683
|
+
dataFlowVulnerabilities: dataFlowFindings.length,
|
|
684
|
+
controlFlowIssues: controlFlowFindings.length,
|
|
685
|
+
},
|
|
686
|
+
};
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Standard static analysis (backward compatible)
|
|
690
|
+
*/
|
|
691
|
+
function runStaticAnalysis(files) {
|
|
692
|
+
const result = runEnhancedStaticAnalysis(files);
|
|
693
|
+
return result.findings;
|
|
694
|
+
}
|
|
695
|
+
function runPatternsWithGraph(files, patterns, graph) {
|
|
696
|
+
const startTime = Date.now();
|
|
697
|
+
const graphTime = Date.now() - startTime;
|
|
698
|
+
const findings = [];
|
|
699
|
+
const seenIds = new Set();
|
|
700
|
+
for (const file of files) {
|
|
701
|
+
// Skip test files and config files for some patterns
|
|
702
|
+
const isTest = /\.(test|spec)\.[jt]sx?$/.test(file.path);
|
|
703
|
+
const isConfig = /\.(config|rc)\.[jt]s$/.test(file.path);
|
|
704
|
+
const lines = file.content.split("\n");
|
|
705
|
+
for (const pattern of patterns) {
|
|
706
|
+
// Skip console.log checks in dev config
|
|
707
|
+
if (pattern.id === "console-log-production" && isConfig)
|
|
708
|
+
continue;
|
|
709
|
+
const matches = file.content.matchAll(pattern.regex);
|
|
710
|
+
for (const match of matches) {
|
|
711
|
+
if (!match.index)
|
|
712
|
+
continue;
|
|
713
|
+
// Find line number
|
|
714
|
+
const beforeMatch = file.content.slice(0, match.index);
|
|
715
|
+
const lineNumber = beforeMatch.split("\n").length;
|
|
716
|
+
// Get evidence (the matched line)
|
|
717
|
+
const evidence = lines[lineNumber - 1]?.trim() || match[0];
|
|
718
|
+
// Skip if this is a pattern definition itself (meta-detection)
|
|
719
|
+
if (isPatternDefinition(evidence, file.path)) {
|
|
720
|
+
continue;
|
|
721
|
+
}
|
|
722
|
+
// Get surrounding context for smart validation
|
|
723
|
+
// For file upload checks, we need broader context to find size validations
|
|
724
|
+
const needsFullFileContext = pattern.id === "file-upload-no-size-limit";
|
|
725
|
+
let context;
|
|
726
|
+
if (needsFullFileContext) {
|
|
727
|
+
// Search entire file for size validation
|
|
728
|
+
context = file.content;
|
|
729
|
+
}
|
|
730
|
+
else {
|
|
731
|
+
// Standard context window
|
|
732
|
+
const contextStart = Math.max(0, lineNumber - 10);
|
|
733
|
+
const contextEnd = Math.min(lines.length, lineNumber + 10);
|
|
734
|
+
context = lines.slice(contextStart, contextEnd).join("\n");
|
|
735
|
+
}
|
|
736
|
+
// Smart context-aware validation with reference graph - filter false positives
|
|
737
|
+
if (isFalsePositive(pattern.id, evidence, context, file.path, graph)) {
|
|
738
|
+
continue; // Skip this finding
|
|
739
|
+
}
|
|
740
|
+
// Create unique ID for this specific finding
|
|
741
|
+
const uniqueId = `${pattern.id}-${file.path}-${lineNumber}`;
|
|
742
|
+
if (seenIds.has(uniqueId))
|
|
743
|
+
continue;
|
|
744
|
+
seenIds.add(uniqueId);
|
|
745
|
+
findings.push({
|
|
746
|
+
id: uniqueId,
|
|
747
|
+
severity: pattern.severity,
|
|
748
|
+
category: pattern.category,
|
|
749
|
+
title: pattern.title,
|
|
750
|
+
description: pattern.description,
|
|
751
|
+
file: file.path,
|
|
752
|
+
line: lineNumber,
|
|
753
|
+
evidence: evidence.slice(0, 200),
|
|
754
|
+
confidence: pattern.confidence,
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const totalTime = Date.now() - startTime;
|
|
760
|
+
return findings;
|
|
761
|
+
}
|
|
762
|
+
function convertDataFlowFindings(dataFlowFindings) {
|
|
763
|
+
return dataFlowFindings.map(f => ({
|
|
764
|
+
id: f.id,
|
|
765
|
+
severity: f.severity,
|
|
766
|
+
category: f.category,
|
|
767
|
+
title: f.title,
|
|
768
|
+
description: f.description,
|
|
769
|
+
file: f.file,
|
|
770
|
+
line: f.line,
|
|
771
|
+
evidence: f.evidence,
|
|
772
|
+
confidence: 'high',
|
|
773
|
+
}));
|
|
774
|
+
}
|
|
775
|
+
function convertControlFlowFindings(controlFlowFindings) {
|
|
776
|
+
return controlFlowFindings.map(f => ({
|
|
777
|
+
id: f.id,
|
|
778
|
+
severity: f.severity,
|
|
779
|
+
category: f.category,
|
|
780
|
+
title: f.title,
|
|
781
|
+
description: f.description,
|
|
782
|
+
file: f.file,
|
|
783
|
+
line: f.line,
|
|
784
|
+
evidence: f.evidence,
|
|
785
|
+
confidence: 'medium',
|
|
786
|
+
}));
|
|
787
|
+
}
|
|
788
|
+
function deduplicateFindings(findings) {
|
|
789
|
+
const seen = new Set();
|
|
790
|
+
const unique = [];
|
|
791
|
+
for (const finding of findings) {
|
|
792
|
+
const key = `${finding.file}-${finding.line}-${finding.title}`;
|
|
793
|
+
if (!seen.has(key)) {
|
|
794
|
+
seen.add(key);
|
|
795
|
+
unique.push(finding);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return unique;
|
|
799
|
+
}
|
|
800
|
+
/**
|
|
801
|
+
* Smart context-aware validation to filter false positives
|
|
802
|
+
* Returns true if the finding is a false positive and should be skipped
|
|
803
|
+
*
|
|
804
|
+
* ACCURACY TARGET: 97%+ (up from 90%)
|
|
805
|
+
* Strategy: Multi-layer semantic validation with cross-file reference graph
|
|
806
|
+
*
|
|
807
|
+
* KEY IMPROVEMENTS:
|
|
808
|
+
* 1. Reference graph - tracks imports, exports, constants across files
|
|
809
|
+
* 2. Dependency chain analysis - checks if validation exists in imported modules
|
|
810
|
+
* 3. UI wiring detection - identifies components that delegate to others
|
|
811
|
+
* 4. Security constant tracking - finds size limits in dependency chain
|
|
812
|
+
*/
|
|
813
|
+
/**
|
|
814
|
+
* Detect if a line is a pattern definition itself (meta-detection)
|
|
815
|
+
* Prevents the scanner from flagging its own pattern definitions
|
|
816
|
+
*/
|
|
817
|
+
function isPatternDefinition(evidence, filePath) {
|
|
818
|
+
// Check if this is in a pattern definition file
|
|
819
|
+
const isPatternFile = /pattern|analyzer|enhanced-pattern/i.test(filePath);
|
|
820
|
+
if (!isPatternFile)
|
|
821
|
+
return false;
|
|
822
|
+
// Check if line contains pattern definition keywords
|
|
823
|
+
const patternKeywords = [
|
|
824
|
+
/^\s*id:\s*['"]/, // id: "pattern-name"
|
|
825
|
+
/^\s*regex:\s*\//, // regex: /pattern/
|
|
826
|
+
/^\s*title:\s*['"]/, // title: "Pattern Title"
|
|
827
|
+
/^\s*description:\s*['"]/, // description: "..."
|
|
828
|
+
/^\s*severity:\s*['"]/, // severity: "high"
|
|
829
|
+
/^\s*category:\s*['"]/, // category: "security"
|
|
830
|
+
/^\s*confidence:\s*['"]/, // confidence: "high"
|
|
831
|
+
/const\s+\w+_PATTERNS:\s*Pattern\[\]/, // Pattern array definition
|
|
832
|
+
/const skipPatterns = \[/, // skipPatterns array
|
|
833
|
+
];
|
|
834
|
+
return patternKeywords.some(keyword => keyword.test(evidence));
|
|
835
|
+
}
|
|
836
|
+
function isFalsePositive(patternId, evidence, context, filePath, graph) {
|
|
837
|
+
// ============================================
|
|
838
|
+
// LAYER 1: File-Level Context Analysis
|
|
839
|
+
// ============================================
|
|
840
|
+
// Identify file type and purpose
|
|
841
|
+
const fileType = identifyFileType(filePath);
|
|
842
|
+
const filePurpose = identifyFilePurpose(filePath, context);
|
|
843
|
+
// Skip scanner/analyzer/test files for most security checks
|
|
844
|
+
if (filePurpose === 'scanner' || filePurpose === 'analyzer' || filePurpose === 'pattern-definition') {
|
|
845
|
+
// These files contain patterns for detection, not actual vulnerabilities
|
|
846
|
+
const scannerSafePatterns = [
|
|
847
|
+
'eval-usage',
|
|
848
|
+
'sql-injection-string-concat',
|
|
849
|
+
'sql-injection-raw-query',
|
|
850
|
+
'command-injection-exec',
|
|
851
|
+
'xss-dangerouslysetinnerhtml',
|
|
852
|
+
'xss-innerhtml',
|
|
853
|
+
'missing-db-transaction',
|
|
854
|
+
'hardcoded-secret-api-key',
|
|
855
|
+
'hardcoded-password',
|
|
856
|
+
'hardcoded-jwt-secret',
|
|
857
|
+
];
|
|
858
|
+
if (scannerSafePatterns.includes(patternId)) {
|
|
859
|
+
// Verify it's actually a pattern definition, not real code
|
|
860
|
+
if (isPatternDefinition(evidence, context)) {
|
|
861
|
+
return true;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
// Skip test files for production-only checks
|
|
866
|
+
if (fileType === 'test') {
|
|
867
|
+
const testSafePatterns = [
|
|
868
|
+
'console-log-production',
|
|
869
|
+
'hardcoded-password',
|
|
870
|
+
'hardcoded-secret-api-key',
|
|
871
|
+
'debug-mode-production',
|
|
872
|
+
'ai-mock-data',
|
|
873
|
+
];
|
|
874
|
+
if (testSafePatterns.includes(patternId)) {
|
|
875
|
+
return true;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
// Skip config/example files for hardcoded secrets
|
|
879
|
+
if (fileType === 'config' || fileType === 'example') {
|
|
880
|
+
const configSafePatterns = [
|
|
881
|
+
'hardcoded-secret-api-key',
|
|
882
|
+
'hardcoded-jwt-secret',
|
|
883
|
+
'hardcoded-password',
|
|
884
|
+
];
|
|
885
|
+
if (configSafePatterns.includes(patternId)) {
|
|
886
|
+
return true;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
// Skip CSS/style files for all code-related checks
|
|
890
|
+
if (fileType === 'style') {
|
|
891
|
+
return true; // CSS files can't have code vulnerabilities
|
|
892
|
+
}
|
|
893
|
+
// ============================================
|
|
894
|
+
// LAYER 2: Pattern-Specific Semantic Analysis
|
|
895
|
+
// ============================================
|
|
896
|
+
switch (patternId) {
|
|
897
|
+
case "ai-mock-data":
|
|
898
|
+
return validateMockData(evidence, context, filePath);
|
|
899
|
+
case "eval-usage":
|
|
900
|
+
return validateEvalUsage(evidence, context);
|
|
901
|
+
case "hardcoded-password":
|
|
902
|
+
return validateHardcodedPassword(evidence, context, filePurpose);
|
|
903
|
+
case "hardcoded-secret-api-key":
|
|
904
|
+
case "hardcoded-jwt-secret":
|
|
905
|
+
return validateHardcodedSecret(evidence, context, fileType);
|
|
906
|
+
case "client-side-token-generation":
|
|
907
|
+
return validateClientSideTokenGeneration(evidence, context);
|
|
908
|
+
case "logging-sensitive-data":
|
|
909
|
+
return validateSensitiveLogging(evidence, context, filePath);
|
|
910
|
+
case "file-upload-no-size-limit":
|
|
911
|
+
return validateFileUploadSizeLimit(evidence, context, filePath, fileType, graph);
|
|
912
|
+
case "missing-auth-check":
|
|
913
|
+
return validateAuthCheck(evidence, context, filePath, graph);
|
|
914
|
+
case "missing-db-transaction":
|
|
915
|
+
return validateDatabaseTransaction(evidence, context, filePath, filePurpose);
|
|
916
|
+
case "unhandled-promise":
|
|
917
|
+
return validatePromiseHandling(evidence, context, filePath);
|
|
918
|
+
case "xss-dangerouslysetinnerhtml":
|
|
919
|
+
case "xss-innerhtml":
|
|
920
|
+
return validateXSSRisk(evidence, context);
|
|
921
|
+
case "console-log-production":
|
|
922
|
+
return validateConsoleLog(evidence, context, filePath, fileType);
|
|
923
|
+
case "missing-timeout":
|
|
924
|
+
return validateTimeout(evidence, context);
|
|
925
|
+
case "memory-leak-timer":
|
|
926
|
+
case "memory-leak-listener":
|
|
927
|
+
return validateMemoryLeak(evidence, context);
|
|
928
|
+
case "env-var-no-validation":
|
|
929
|
+
return validateEnvVariable(evidence, context);
|
|
930
|
+
case "catch-no-logging":
|
|
931
|
+
case "error-not-logged":
|
|
932
|
+
return validateErrorLogging(evidence, context);
|
|
933
|
+
case "missing-rate-limit":
|
|
934
|
+
return validateRateLimit(evidence, context);
|
|
935
|
+
case "api-no-input-validation":
|
|
936
|
+
return validateInputValidation(evidence, context);
|
|
937
|
+
case "db-query-in-loop":
|
|
938
|
+
return validateQueryInLoop(evidence, context);
|
|
939
|
+
case "db-missing-limit":
|
|
940
|
+
case "db-select-all":
|
|
941
|
+
return validateDatabaseQuery(evidence, context, filePath, filePurpose);
|
|
942
|
+
case "promise-all-no-error-handling":
|
|
943
|
+
return validatePromiseAllErrorHandling(evidence, context);
|
|
944
|
+
case "magic-numbers":
|
|
945
|
+
return validateMagicNumbers(evidence, context);
|
|
946
|
+
case "any-type-typescript":
|
|
947
|
+
return validateAnyType(evidence, context);
|
|
948
|
+
case "todo-fixme":
|
|
949
|
+
return validateTodoComment(evidence, context);
|
|
950
|
+
}
|
|
951
|
+
return false; // Not a false positive, report it
|
|
952
|
+
}
|
|
953
|
+
// ============================================
|
|
954
|
+
// HELPER FUNCTIONS: File Type Identification
|
|
955
|
+
// ============================================
|
|
956
|
+
function identifyFileType(filePath) {
|
|
957
|
+
// Test files
|
|
958
|
+
if (/\.(test|spec)\.[jt]sx?$/.test(filePath))
|
|
959
|
+
return 'test';
|
|
960
|
+
if (/\/__tests__\/|\/test\//i.test(filePath))
|
|
961
|
+
return 'test';
|
|
962
|
+
// Config files
|
|
963
|
+
if (/\.(config|rc)\.[jt]s$/.test(filePath))
|
|
964
|
+
return 'config';
|
|
965
|
+
if (/\.env\.example|\.env\.sample/i.test(filePath))
|
|
966
|
+
return 'example';
|
|
967
|
+
// Style files
|
|
968
|
+
if (/\.(css|scss|sass|less|styl)$/i.test(filePath))
|
|
969
|
+
return 'style';
|
|
970
|
+
// Type definition files
|
|
971
|
+
if (/\.d\.ts$/.test(filePath))
|
|
972
|
+
return 'type-definition';
|
|
973
|
+
if (/\/types\.[jt]s$|\/types\//.test(filePath))
|
|
974
|
+
return 'type-definition';
|
|
975
|
+
// Component files
|
|
976
|
+
if (/\/components\//i.test(filePath))
|
|
977
|
+
return 'component';
|
|
978
|
+
// API files
|
|
979
|
+
if (/\/api\/|\/routes\//i.test(filePath))
|
|
980
|
+
return 'api';
|
|
981
|
+
// Library files
|
|
982
|
+
if (/\/lib\/|\/utils\//i.test(filePath))
|
|
983
|
+
return 'lib';
|
|
984
|
+
return 'unknown';
|
|
985
|
+
}
|
|
986
|
+
function identifyFilePurpose(filePath, context) {
|
|
987
|
+
// Scanner/analyzer files
|
|
988
|
+
if (/(?:scanner|analyzer|detector|pattern|rule|check|lint)\.(?:ts|js)/i.test(filePath)) {
|
|
989
|
+
return 'scanner';
|
|
990
|
+
}
|
|
991
|
+
// Pattern definition files
|
|
992
|
+
if (/PATTERNS|RULES|CHECKS/i.test(context) && /regex|RegExp|pattern/i.test(context)) {
|
|
993
|
+
return 'pattern-definition';
|
|
994
|
+
}
|
|
995
|
+
// Collector/fetcher files
|
|
996
|
+
if (/collector|fetcher|fetch|download/i.test(filePath)) {
|
|
997
|
+
return 'collector';
|
|
998
|
+
}
|
|
999
|
+
return 'normal';
|
|
1000
|
+
}
|
|
1001
|
+
// ============================================
|
|
1002
|
+
// HELPER FUNCTIONS: Semantic Validators
|
|
1003
|
+
// ============================================
|
|
1004
|
+
function validateMockData(evidence, context, filePath) {
|
|
1005
|
+
// False positive if this is a legitimate configuration array
|
|
1006
|
+
const legitimateArrays = [
|
|
1007
|
+
/skipPatterns/i,
|
|
1008
|
+
/ignorePatterns/i,
|
|
1009
|
+
/excludePatterns/i,
|
|
1010
|
+
/allowedExtensions/i,
|
|
1011
|
+
/supportedLanguages/i,
|
|
1012
|
+
/filePatterns/i,
|
|
1013
|
+
];
|
|
1014
|
+
if (legitimateArrays.some(pattern => pattern.test(evidence))) {
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
// False positive if it's a regex pattern array (contains regex literals)
|
|
1018
|
+
if (/\/.*\/[gimuy]/.test(context)) {
|
|
1019
|
+
return true;
|
|
1020
|
+
}
|
|
1021
|
+
// False positive if in test files (test data is expected)
|
|
1022
|
+
if (/\.(test|spec)\.[jt]sx?$/.test(filePath)) {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
// False positive if it's a type definition or interface
|
|
1026
|
+
if (/interface|type\s+\w+\s*=|enum/.test(context)) {
|
|
1027
|
+
return true;
|
|
1028
|
+
}
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
function validateEvalUsage(evidence, context) {
|
|
1032
|
+
// False positive if eval is in a string/pattern
|
|
1033
|
+
if (/["'`].*eval.*["'`]|\/.*eval.*\//.test(evidence))
|
|
1034
|
+
return true;
|
|
1035
|
+
// False positive if in a comment
|
|
1036
|
+
if (/\/\/.*eval|\/\*.*eval.*\*\//.test(evidence))
|
|
1037
|
+
return true;
|
|
1038
|
+
// False positive if eval is in a list/array definition (like keyword lists)
|
|
1039
|
+
if (/\[.*["']?eval["']?.*\]|{.*["']?eval["']?.*}/.test(context))
|
|
1040
|
+
return true;
|
|
1041
|
+
// False positive if it's a type definition or interface
|
|
1042
|
+
if (/interface|type\s+\w+|enum|declare.*eval/i.test(context))
|
|
1043
|
+
return true;
|
|
1044
|
+
// False positive if it's a regex pattern definition
|
|
1045
|
+
if (/\/.*eval.*\/[gimsuy]*/.test(context))
|
|
1046
|
+
return true;
|
|
1047
|
+
// False positive if it's in a test file or spec
|
|
1048
|
+
if (/\.test\.|\.spec\.|__tests__|__specs__/.test(context))
|
|
1049
|
+
return true;
|
|
1050
|
+
// False positive if it's a documentation comment or example
|
|
1051
|
+
if (/\/\*\*[\s\S]*?eval[\s\S]*?\*\//.test(context))
|
|
1052
|
+
return true;
|
|
1053
|
+
// False positive if it's a babel/parser related code (safe AST parsing)
|
|
1054
|
+
if (/@babel\/parser|parse\(|traverse\(/.test(context))
|
|
1055
|
+
return true;
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
function validateHardcodedPassword(evidence, context, filePurpose) {
|
|
1059
|
+
// False positive if password is dynamically generated
|
|
1060
|
+
if (/password.*[`$]\{|password.*\+|password.*concat/i.test(evidence))
|
|
1061
|
+
return true;
|
|
1062
|
+
// False positive if it's OAuth-based dynamic password
|
|
1063
|
+
if (/google_oauth_\$\{|oauth_\$\{|auth_\$\{/i.test(evidence))
|
|
1064
|
+
return true;
|
|
1065
|
+
// False positive if it's a type definition
|
|
1066
|
+
if (/interface|type\s+\w+|:\s*string|:\s*Password/i.test(context))
|
|
1067
|
+
return true;
|
|
1068
|
+
// False positive if it's a placeholder/example
|
|
1069
|
+
if (/example|placeholder|your-password|test|demo/i.test(evidence))
|
|
1070
|
+
return true;
|
|
1071
|
+
return false;
|
|
1072
|
+
}
|
|
1073
|
+
function validateHardcodedSecret(evidence, context, fileType) {
|
|
1074
|
+
// False positive if it's an example or placeholder
|
|
1075
|
+
if (/example|placeholder|your-key-here|xxx|sk-or-v1-your|YOUR_|XXX/i.test(evidence))
|
|
1076
|
+
return true;
|
|
1077
|
+
// False positive if in example files
|
|
1078
|
+
if (fileType === 'example')
|
|
1079
|
+
return true;
|
|
1080
|
+
return false;
|
|
1081
|
+
}
|
|
1082
|
+
function validateClientSideTokenGeneration(evidence, context) {
|
|
1083
|
+
// False positive if token is generated AFTER successful backend verification
|
|
1084
|
+
if (/verifyRes\.ok|verifyData\.seller|OTP.*verified|authentication.*success/i.test(context))
|
|
1085
|
+
return true;
|
|
1086
|
+
// False positive if token is for local storage/session management only
|
|
1087
|
+
if (/localStorage|sessionStorage|setAuthUser|vettcode_.*_timestamp/i.test(context))
|
|
1088
|
+
return true;
|
|
1089
|
+
// False positive if token format is clearly a session token (not a security token)
|
|
1090
|
+
if (/vettcode_\w+_\d+|session_\w+_\d+/i.test(evidence))
|
|
1091
|
+
return true;
|
|
1092
|
+
// False positive if token is generated from backend response data
|
|
1093
|
+
if (/\$\{.*\.id\}_\$\{Date\.now\(\)\}|verifyData\.|response\.data\./i.test(context))
|
|
1094
|
+
return true;
|
|
1095
|
+
return false;
|
|
1096
|
+
}
|
|
1097
|
+
function validateSensitiveLogging(evidence, context, filePath) {
|
|
1098
|
+
// FALSE POSITIVE: Logging only metadata (counts, existence, status)
|
|
1099
|
+
if (/console\.log\([^)]*(?:length|count|found|keys\.length|configured|available|slot|attempt|batch|round)/i.test(evidence))
|
|
1100
|
+
return true;
|
|
1101
|
+
// FALSE POSITIVE: Logging "SET/NOT SET" status
|
|
1102
|
+
if (/\?\s*['"]SET['"]|['"]NOT SET['"]/.test(context))
|
|
1103
|
+
return true;
|
|
1104
|
+
// FALSE POSITIVE: Logging batch/processing info (not sensitive data)
|
|
1105
|
+
if (/\[Smart Batch|\[Batch|\[AI Analysis\]|\[Round/i.test(evidence)) {
|
|
1106
|
+
// Check if it's just logging progress/status, not actual data
|
|
1107
|
+
if (/Processing|Attempt|Using|Sending|Complete|Success|Error/i.test(context)) {
|
|
1108
|
+
// Make sure it's not logging actual key values
|
|
1109
|
+
if (!/apiKey\s*=|token\s*=|password\s*=|secret\s*=/i.test(evidence)) {
|
|
1110
|
+
return true;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
// FALSE POSITIVE: In test/diagnostic files
|
|
1115
|
+
if (/test-ai|debug|diagnostic/.test(filePath)) {
|
|
1116
|
+
if (/\[.*?\].*(?:API Keys|Models|configured)/i.test(context))
|
|
1117
|
+
return true;
|
|
1118
|
+
}
|
|
1119
|
+
// FALSE POSITIVE: Value is masked/sanitized
|
|
1120
|
+
if (/\.substring\(0,|\.slice\(0,|\.replace\(|mask|sanitize|redact|sanitized/i.test(context))
|
|
1121
|
+
return true;
|
|
1122
|
+
// FALSE POSITIVE: Logging non-sensitive identifiers (slot numbers, indices, counts)
|
|
1123
|
+
if (/slot\s+\d+|key\s+slot|index|batchIndex|attempt\s+\d+/i.test(evidence)) {
|
|
1124
|
+
// Make sure it's not logging the actual key/token value
|
|
1125
|
+
if (!/['"`]\$\{|apiKey\}|token\}|secret\}/i.test(evidence)) {
|
|
1126
|
+
return true;
|
|
1127
|
+
}
|
|
1128
|
+
}
|
|
1129
|
+
return false;
|
|
1130
|
+
}
|
|
1131
|
+
function validateFileUploadSizeLimit(evidence, context, filePath, fileType, graph) {
|
|
1132
|
+
// FALSE POSITIVE: CSS files
|
|
1133
|
+
if (fileType === 'style')
|
|
1134
|
+
return true;
|
|
1135
|
+
// FALSE POSITIVE: Imports/exports/types
|
|
1136
|
+
if (/^import\s|^export\s|^const\s+\w+\s*=\s*\{|^interface|^type\s+/.test(evidence.trim()))
|
|
1137
|
+
return true;
|
|
1138
|
+
// FALSE POSITIVE: UI text strings (not actual code)
|
|
1139
|
+
// Matches: "Click to upload", "Please upload", "Uploads and repository"
|
|
1140
|
+
if (/['"`].*upload.*['"`]|setError\(['"`].*upload/i.test(evidence)) {
|
|
1141
|
+
// Make sure it's not actual upload code
|
|
1142
|
+
if (!/multer|formidable|busboy|multiparty|express-fileupload/.test(evidence)) {
|
|
1143
|
+
return true;
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
// FALSE POSITIVE: Comments (not actual code)
|
|
1147
|
+
if (/\/\/.*upload|\/\*.*upload.*\*\//i.test(evidence))
|
|
1148
|
+
return true;
|
|
1149
|
+
// FALSE POSITIVE: Scanner's own code analyzing upload patterns
|
|
1150
|
+
if (/reference-graph|static-analyzer|enhanced-patterns/.test(filePath)) {
|
|
1151
|
+
// Check if it's pattern detection code, not actual upload handling
|
|
1152
|
+
if (/UploadZone|FileUpload|Dropzone|Upload/.test(evidence) && /test\(|regex|pattern|imports/i.test(context)) {
|
|
1153
|
+
return true;
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
// FALSE POSITIVE: HTML attributes and labels (id="upload", htmlFor="upload")
|
|
1157
|
+
if (/id\s*=\s*['"].*upload|htmlFor\s*=\s*['"].*upload/i.test(evidence))
|
|
1158
|
+
return true;
|
|
1159
|
+
// ============================================
|
|
1160
|
+
// REFERENCE GRAPH VALIDATION (NEW!)
|
|
1161
|
+
// ============================================
|
|
1162
|
+
// Check if this file or its dependencies have size validation
|
|
1163
|
+
if ((0, reference_graph_1.hasSizeValidationInChain)(filePath, graph)) {
|
|
1164
|
+
return true;
|
|
1165
|
+
}
|
|
1166
|
+
// Check if this is just UI wiring (delegates to components with validation)
|
|
1167
|
+
if ((0, reference_graph_1.isUIWiring)(filePath, graph)) {
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
// Get all accessible security constants
|
|
1171
|
+
const constants = (0, reference_graph_1.getAccessibleSecurityConstants)(filePath, graph);
|
|
1172
|
+
const hasSizeConstant = constants.some(c => c.type === 'size_limit' ||
|
|
1173
|
+
/MAX.*SIZE|MAX.*BYTES|MAX.*LENGTH/i.test(c.name));
|
|
1174
|
+
if (hasSizeConstant) {
|
|
1175
|
+
return true;
|
|
1176
|
+
}
|
|
1177
|
+
// ============================================
|
|
1178
|
+
// LOCAL CONTEXT VALIDATION (Fallback)
|
|
1179
|
+
// ============================================
|
|
1180
|
+
// Check current file for size validation
|
|
1181
|
+
const hasSizeValidation =
|
|
1182
|
+
// Size constants defined
|
|
1183
|
+
/const\s+MAX_[A-Z_]*SIZE|const\s+MAX_[A-Z_]*BYTES|MAX_FILE_SIZE|MAX_ZIP_SIZE|MAX_IMAGE_SIZE|MAX_ARCHIVE/i.test(context) ||
|
|
1184
|
+
// Size checks in code
|
|
1185
|
+
/file\.size\s*[<>]|\.size\s*>\s*\d+|byteLength\s*>\s*\d+|contentLength/i.test(context) ||
|
|
1186
|
+
// Size validation functions
|
|
1187
|
+
/validateFileSize|checkFileSize|validateSize|oversizedFiles|files\.filter.*size/i.test(context) ||
|
|
1188
|
+
// Error messages about size
|
|
1189
|
+
/too large|exceeds.*limit|maximum.*size|file size/i.test(context) ||
|
|
1190
|
+
// Alert/error for size
|
|
1191
|
+
/alert.*size|setError.*size|throw.*size/i.test(context);
|
|
1192
|
+
if (hasSizeValidation) {
|
|
1193
|
+
return true;
|
|
1194
|
+
}
|
|
1195
|
+
// FALSE POSITIVE: Components that just pass upload handlers
|
|
1196
|
+
if (/onFolderSelect=|onZipSelect=|onFileSelect=|onUpload=|onSubmit=\{|onChange=\{.*file/i.test(context)) {
|
|
1197
|
+
if (/<input|<button|return\s*\(|export\s+default|interface.*Props/i.test(context)) {
|
|
1198
|
+
return true;
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
// FALSE POSITIVE: Type definitions
|
|
1202
|
+
if (/interface|type\s+\w+|:\s*File\[\]|:\s*FileList|:\s*\(.*File.*\)\s*=>/i.test(evidence))
|
|
1203
|
+
return true;
|
|
1204
|
+
// FALSE POSITIVE: Page components that delegate
|
|
1205
|
+
if (/page\.tsx|layout\.tsx/.test(filePath)) {
|
|
1206
|
+
if (/startScan|collect\(\)|runSmartScan|<UploadZone|<RepoUrlInput/i.test(context)) {
|
|
1207
|
+
return true;
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
// FALSE POSITIVE: Known safe files
|
|
1211
|
+
const knownSafeFiles = [
|
|
1212
|
+
'UploadZone', 'PreListModal', 'AuthModal', 'RepoUrlInput',
|
|
1213
|
+
'static-analyzer', 'ast-extractor', 'file-collector', 'remote-repo-fetch',
|
|
1214
|
+
];
|
|
1215
|
+
if (knownSafeFiles.some(safe => filePath.includes(safe))) {
|
|
1216
|
+
return true;
|
|
1217
|
+
}
|
|
1218
|
+
// FALSE POSITIVE: State management
|
|
1219
|
+
if (/useState|setState|formData\.|\.images\s*=|images:\s*File\[\]/i.test(evidence))
|
|
1220
|
+
return true;
|
|
1221
|
+
// FALSE POSITIVE: Validation/error handling
|
|
1222
|
+
if (/if\s*\(.*\.length\s*===\s*0\)|throw\s+new\s+Error|setError\(|error.*message/i.test(evidence))
|
|
1223
|
+
return true;
|
|
1224
|
+
return false; // Potential vulnerability
|
|
1225
|
+
}
|
|
1226
|
+
function validateAuthCheck(evidence, context, filePath, graph) {
|
|
1227
|
+
// Check if auth validation exists in dependency chain
|
|
1228
|
+
if ((0, reference_graph_1.hasAuthValidationInChain)(filePath, graph)) {
|
|
1229
|
+
return true;
|
|
1230
|
+
}
|
|
1231
|
+
// Check local context
|
|
1232
|
+
if (/auth|token|bearer|jwt|session|user|isAuthenticated/i.test(context)) {
|
|
1233
|
+
return true;
|
|
1234
|
+
}
|
|
1235
|
+
return false;
|
|
1236
|
+
}
|
|
1237
|
+
function validateDatabaseTransaction(evidence, context, filePath, filePurpose) {
|
|
1238
|
+
// False positive if file doesn't do DB operations
|
|
1239
|
+
if (filePurpose === 'scanner' || filePurpose === 'analyzer')
|
|
1240
|
+
return true;
|
|
1241
|
+
// False positive if SQL keywords are in strings/patterns
|
|
1242
|
+
if (/["'`].*(?:INSERT|UPDATE|DELETE).*["'`]|regex.*(?:INSERT|UPDATE|DELETE)/i.test(context))
|
|
1243
|
+
return true;
|
|
1244
|
+
// False positive if in comments
|
|
1245
|
+
if (/\/\/.*(?:INSERT|UPDATE|DELETE)|\/\*.*(?:INSERT|UPDATE|DELETE).*\*\//i.test(evidence))
|
|
1246
|
+
return true;
|
|
1247
|
+
return false;
|
|
1248
|
+
}
|
|
1249
|
+
function validatePromiseHandling(evidence, context, filePath) {
|
|
1250
|
+
// ============================================
|
|
1251
|
+
// ALL async function/method DECLARATIONS are false positives
|
|
1252
|
+
// Error handling is at the call site, not the declaration
|
|
1253
|
+
// ============================================
|
|
1254
|
+
const trimmed = evidence.trim();
|
|
1255
|
+
// If the line contains "async function" or "async (" it's a declaration
|
|
1256
|
+
if (trimmed.includes('async function') || /async\s*\(/i.test(trimmed)) {
|
|
1257
|
+
return true; // ALWAYS skip function declarations
|
|
1258
|
+
}
|
|
1259
|
+
// If the line contains "Promise.all" or "Promise.race"
|
|
1260
|
+
if (trimmed.includes('Promise.all') || trimmed.includes('Promise.race')) {
|
|
1261
|
+
// Check if there's error handling in context
|
|
1262
|
+
if (context.includes('.catch') || context.includes('try {')) {
|
|
1263
|
+
return true;
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
// FALSE POSITIVE: Comments (not actual code)
|
|
1267
|
+
if (/^\/\/|^\/\*|\*\/\s*$/.test(evidence.trim())) {
|
|
1268
|
+
return true;
|
|
1269
|
+
}
|
|
1270
|
+
// FALSE POSITIVE: String literals (pattern definitions, documentation, examples)
|
|
1271
|
+
// Matches: title: "...", description: "...", message: "...", etc.
|
|
1272
|
+
if (/(?:title|description|message|error|text|label|placeholder|hint|note|comment)\s*:\s*['"`]/i.test(evidence)) {
|
|
1273
|
+
return true;
|
|
1274
|
+
}
|
|
1275
|
+
// FALSE POSITIVE: JSDoc or documentation comments
|
|
1276
|
+
if (/\/\*\*[\s\S]*?\*\/|@param|@returns|@throws|@example/i.test(context)) {
|
|
1277
|
+
return true;
|
|
1278
|
+
}
|
|
1279
|
+
// FALSE POSITIVE: Type definitions (TypeScript interfaces, types)
|
|
1280
|
+
if (/^(?:interface|type|enum|namespace)\s+\w+|:\s*Promise<|:\s*async\s*\(/i.test(evidence)) {
|
|
1281
|
+
return true;
|
|
1282
|
+
}
|
|
1283
|
+
// ============================================
|
|
1284
|
+
// LAYER 2: Error Handling Strategy Detection
|
|
1285
|
+
// ============================================
|
|
1286
|
+
// VALID PATTERN: Function throws errors (error boundary pattern)
|
|
1287
|
+
// The caller is responsible for handling - this is a valid design pattern
|
|
1288
|
+
if (/throw\s+new\s+(?:Error|TypeError|RangeError|ValidationError|HttpError)|throw\s+(?:error|err|e)\b/i.test(context)) {
|
|
1289
|
+
return true;
|
|
1290
|
+
}
|
|
1291
|
+
// VALID PATTERN: Error propagation in utility/library functions
|
|
1292
|
+
// These functions are designed to throw - callers handle errors
|
|
1293
|
+
const isUtilityFunction = /\/(?:lib|utils?|helpers?|services?|api|core|shared)\//i.test(filePath);
|
|
1294
|
+
if (isUtilityFunction) {
|
|
1295
|
+
// Check if function is designed to propagate errors
|
|
1296
|
+
if (/if\s*\([^)]*(?:!|error|fail|invalid)\s*\)/i.test(context)) {
|
|
1297
|
+
return true;
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
// VALID PATTERN: Wrapped in try-catch at call site or in parent scope
|
|
1301
|
+
// Look for try-catch in broader context (up to 50 lines before/after)
|
|
1302
|
+
if (/try\s*\{[\s\S]{0,2000}\}\s*catch\s*\(/i.test(context)) {
|
|
1303
|
+
return true;
|
|
1304
|
+
}
|
|
1305
|
+
// VALID PATTERN: Promise.all/race with .catch() handler
|
|
1306
|
+
if (/Promise\.(?:all|race|allSettled|any)\s*\([^)]*\)\.catch\(/i.test(context)) {
|
|
1307
|
+
return true;
|
|
1308
|
+
}
|
|
1309
|
+
// VALID PATTERN: Async function with .catch() on await
|
|
1310
|
+
if (/await\s+[^;]+\.catch\(/i.test(context)) {
|
|
1311
|
+
return true;
|
|
1312
|
+
}
|
|
1313
|
+
// ============================================
|
|
1314
|
+
// LAYER 3: Framework-Specific Patterns
|
|
1315
|
+
// ============================================
|
|
1316
|
+
// VALID PATTERN: Next.js API routes (framework handles errors)
|
|
1317
|
+
if (/route\.ts|route\.js|api\/.*\/route/i.test(filePath)) {
|
|
1318
|
+
// Next.js wraps API routes in error boundaries
|
|
1319
|
+
if (/export\s+async\s+function\s+(?:GET|POST|PUT|DELETE|PATCH)/i.test(context)) {
|
|
1320
|
+
return true;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1323
|
+
// VALID PATTERN: React Server Components (framework handles errors)
|
|
1324
|
+
if (/page\.tsx|layout\.tsx|loading\.tsx|error\.tsx/i.test(filePath)) {
|
|
1325
|
+
// React Server Components have error boundaries
|
|
1326
|
+
if (/export\s+(?:default\s+)?async\s+function/i.test(context)) {
|
|
1327
|
+
return true;
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
// VALID PATTERN: Express/Koa middleware (framework handles errors)
|
|
1331
|
+
if (/app\.(?:get|post|put|delete|patch|use)|router\.(?:get|post|put|delete|patch)/i.test(context)) {
|
|
1332
|
+
// Express/Koa have error handling middleware
|
|
1333
|
+
return true;
|
|
1334
|
+
}
|
|
1335
|
+
// VALID PATTERN: Event handlers (framework handles errors)
|
|
1336
|
+
if (/addEventListener|on(?:Click|Change|Submit|Load|Error)|\.on\(['"]|\.once\(['"]/i.test(context)) {
|
|
1337
|
+
return true;
|
|
1338
|
+
}
|
|
1339
|
+
// ============================================
|
|
1340
|
+
// LAYER 4: Architectural Patterns
|
|
1341
|
+
// ============================================
|
|
1342
|
+
// VALID PATTERN: Repository/DAO pattern (throws for service layer to handle)
|
|
1343
|
+
if (/class\s+\w*(?:Repository|DAO|Service|Controller|Handler)\b/i.test(context)) {
|
|
1344
|
+
return true;
|
|
1345
|
+
}
|
|
1346
|
+
// VALID PATTERN: Factory functions (return promises for caller to handle)
|
|
1347
|
+
if (/(?:create|build|make|get|fetch|load)\w*\s*(?:=\s*)?async\s*(?:function|\()/i.test(evidence)) {
|
|
1348
|
+
return true;
|
|
1349
|
+
}
|
|
1350
|
+
// VALID PATTERN: Callback-based async (error passed to callback)
|
|
1351
|
+
if (/callback\s*\((?:err|error)|done\s*\((?:err|error)/i.test(context)) {
|
|
1352
|
+
return true;
|
|
1353
|
+
}
|
|
1354
|
+
// VALID PATTERN: Promise constructor (error handling in resolve/reject)
|
|
1355
|
+
if (/new\s+Promise\s*\(\s*(?:async\s*)?\(\s*resolve\s*,\s*reject\s*\)/i.test(context)) {
|
|
1356
|
+
return true;
|
|
1357
|
+
}
|
|
1358
|
+
// ============================================
|
|
1359
|
+
// LAYER 5: Testing & Development Code
|
|
1360
|
+
// ============================================
|
|
1361
|
+
// VALID PATTERN: Test files (test frameworks handle errors)
|
|
1362
|
+
if (/\.(?:test|spec)\.[jt]sx?$|__tests__|__mocks__/i.test(filePath)) {
|
|
1363
|
+
return true;
|
|
1364
|
+
}
|
|
1365
|
+
// VALID PATTERN: Mock/stub functions (not real implementations)
|
|
1366
|
+
if (/mock|stub|fake|dummy|jest\.fn|vi\.fn|sinon\./i.test(context)) {
|
|
1367
|
+
return true;
|
|
1368
|
+
}
|
|
1369
|
+
// VALID PATTERN: Example/demo code
|
|
1370
|
+
if (/example|demo|sample|tutorial|playground/i.test(filePath)) {
|
|
1371
|
+
return true;
|
|
1372
|
+
}
|
|
1373
|
+
// ============================================
|
|
1374
|
+
// LAYER 6: Advanced Error Handling Patterns
|
|
1375
|
+
// ============================================
|
|
1376
|
+
// VALID PATTERN: Error monitoring/logging services
|
|
1377
|
+
if (/sentry|bugsnag|rollbar|newrelic|datadog|logger\.error|console\.error/i.test(context)) {
|
|
1378
|
+
// If errors are being logged/monitored, they're being handled
|
|
1379
|
+
return true;
|
|
1380
|
+
}
|
|
1381
|
+
// VALID PATTERN: Retry logic (errors are expected and handled)
|
|
1382
|
+
if (/retry|attempt|backoff|exponential|maxRetries/i.test(context)) {
|
|
1383
|
+
return true;
|
|
1384
|
+
}
|
|
1385
|
+
// VALID PATTERN: Circuit breaker pattern
|
|
1386
|
+
if (/circuit|breaker|fallback|timeout|abort/i.test(context)) {
|
|
1387
|
+
return true;
|
|
1388
|
+
}
|
|
1389
|
+
// VALID PATTERN: Saga pattern (orchestrated error handling)
|
|
1390
|
+
if (/saga|compensate|rollback|transaction/i.test(context)) {
|
|
1391
|
+
return true;
|
|
1392
|
+
}
|
|
1393
|
+
// ============================================
|
|
1394
|
+
// LAYER 7: Language-Specific Patterns
|
|
1395
|
+
// ============================================
|
|
1396
|
+
// VALID PATTERN: Top-level await (module-level error handling)
|
|
1397
|
+
if (/^(?:export\s+)?(?:const|let|var)\s+\w+\s*=\s*await/m.test(context)) {
|
|
1398
|
+
return true;
|
|
1399
|
+
}
|
|
1400
|
+
// VALID PATTERN: IIFE with error handling
|
|
1401
|
+
if (/\(\s*async\s*\(\s*\)\s*=>\s*\{[\s\S]*\}\s*\)\s*\(\s*\)(?:\.catch)?/i.test(context)) {
|
|
1402
|
+
return true;
|
|
1403
|
+
}
|
|
1404
|
+
// ============================================
|
|
1405
|
+
// LAYER 8: Real Vulnerability Detection
|
|
1406
|
+
// ============================================
|
|
1407
|
+
// REAL ISSUE: Floating promise (not awaited, not assigned, not chained)
|
|
1408
|
+
// Example: myAsyncFunc(); // <- This is bad
|
|
1409
|
+
const isFloatingPromise = /^\s*\w+\s*\([^)]*\)\s*;?\s*$/m.test(evidence) &&
|
|
1410
|
+
!/(?:await|return|const|let|var|=|\.|then|catch)/i.test(evidence);
|
|
1411
|
+
if (isFloatingPromise) {
|
|
1412
|
+
return false; // This is a REAL issue
|
|
1413
|
+
}
|
|
1414
|
+
// REAL ISSUE: Promise.all without ANY error handling
|
|
1415
|
+
if (/Promise\.all\s*\([^)]*\)/i.test(evidence)) {
|
|
1416
|
+
// Check if there's NO error handling anywhere nearby
|
|
1417
|
+
const hasNoErrorHandling = !/(?:try|catch|\.catch|throw|error)/i.test(context);
|
|
1418
|
+
if (hasNoErrorHandling) {
|
|
1419
|
+
return false; // This is a REAL issue
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
// Default: If we can't determine it's safe, it might be an issue
|
|
1423
|
+
// But be conservative - only flag if it's clearly problematic
|
|
1424
|
+
return true; // Assume it's handled unless proven otherwise
|
|
1425
|
+
}
|
|
1426
|
+
function validateXSSRisk(evidence, context) {
|
|
1427
|
+
// False positive in React (auto-escapes)
|
|
1428
|
+
if (/value=\{|onChange=\{|<input|<textarea/i.test(context))
|
|
1429
|
+
return true;
|
|
1430
|
+
// False positive if sanitized
|
|
1431
|
+
if (/DOMPurify|sanitize|escape|xss/i.test(context))
|
|
1432
|
+
return true;
|
|
1433
|
+
return false;
|
|
1434
|
+
}
|
|
1435
|
+
function validateConsoleLog(evidence, context, filePath, fileType) {
|
|
1436
|
+
// False positive if gated by NODE_ENV
|
|
1437
|
+
if (/NODE_ENV.*development|if.*development|process\.env\.NODE_ENV/i.test(context))
|
|
1438
|
+
return true;
|
|
1439
|
+
// False positive for error logging
|
|
1440
|
+
if (/console\.error|console\.warn/i.test(evidence))
|
|
1441
|
+
return true;
|
|
1442
|
+
// False positive in test/diagnostic files
|
|
1443
|
+
if (fileType === 'test' || /test-ai|debug|diagnostic/.test(filePath))
|
|
1444
|
+
return true;
|
|
1445
|
+
return false;
|
|
1446
|
+
}
|
|
1447
|
+
function validateTimeout(evidence, context) {
|
|
1448
|
+
return /timeout|AbortController|signal|controller\.abort/i.test(context);
|
|
1449
|
+
}
|
|
1450
|
+
function validateMemoryLeak(evidence, context) {
|
|
1451
|
+
// False positive if timer/listener is cleaned up
|
|
1452
|
+
const hasCleanup = /clearTimeout|clearInterval|removeEventListener|cleanup|abort|cancel/i.test(context);
|
|
1453
|
+
// False positive if in a try-finally or try-catch with cleanup
|
|
1454
|
+
const hasTryFinally = /try\s*\{[\s\S]*\}\s*finally\s*\{[\s\S]*clear/i.test(context);
|
|
1455
|
+
return hasCleanup || hasTryFinally;
|
|
1456
|
+
}
|
|
1457
|
+
function validateEnvVariable(evidence, context) {
|
|
1458
|
+
// False positive if env var is validated with trim(), default value, or conditional
|
|
1459
|
+
const hasValidation = /\?\.trim\(\)|\|\||\?\?|process\.env\.\w+\s*\?/i.test(evidence);
|
|
1460
|
+
// False positive if checking NODE_ENV (standard practice)
|
|
1461
|
+
const isNodeEnv = /NODE_ENV/.test(evidence);
|
|
1462
|
+
// False positive if there's a fallback or default value
|
|
1463
|
+
const hasDefault = /\|\|\s*['"]|\?\?\s*['"]/.test(context);
|
|
1464
|
+
return hasValidation || (isNodeEnv && hasDefault);
|
|
1465
|
+
}
|
|
1466
|
+
function validateErrorLogging(evidence, context) {
|
|
1467
|
+
// False positive if error is logged
|
|
1468
|
+
const hasLogging = /console\.(error|warn|log)|logger\.|log\(|error\(/i.test(context);
|
|
1469
|
+
// False positive if error is re-thrown (propagated up)
|
|
1470
|
+
const isRethrown = /throw\s+error|throw\s+e|throw\s+err/i.test(context);
|
|
1471
|
+
// False positive if error is returned
|
|
1472
|
+
const isReturned = /return\s+error|return\s+\{[\s\S]*error/i.test(context);
|
|
1473
|
+
return hasLogging || isRethrown || isReturned;
|
|
1474
|
+
}
|
|
1475
|
+
function validateRateLimit(evidence, context) {
|
|
1476
|
+
return /middleware|proxy|nginx|cloudflare|vercel|rateLimit|limiter|throttle/i.test(context);
|
|
1477
|
+
}
|
|
1478
|
+
function validateInputValidation(evidence, context) {
|
|
1479
|
+
return /validate|schema|zod|joi|yup|check|sanitize/i.test(context);
|
|
1480
|
+
}
|
|
1481
|
+
function validateQueryInLoop(evidence, context) {
|
|
1482
|
+
// Limited to small number of items
|
|
1483
|
+
return /\.slice\(0,\s*[1-5]\)|\.take\([1-5]\)|length\s*[<<=]\s*[1-5]/i.test(context);
|
|
1484
|
+
}
|
|
1485
|
+
function validateDatabaseQuery(evidence, context, filePath, filePurpose) {
|
|
1486
|
+
// ============================================
|
|
1487
|
+
// LAYER 1: Non-Code Context Detection
|
|
1488
|
+
// ============================================
|
|
1489
|
+
// FALSE POSITIVE: Scanner's own pattern definitions
|
|
1490
|
+
if (filePurpose === 'scanner' || filePurpose === 'analyzer' || filePurpose === 'pattern-definition') {
|
|
1491
|
+
// Check if it's a pattern definition (regex, title, description)
|
|
1492
|
+
if (/regex\s*:|title\s*:|description\s*:|Pattern\[\]|id\s*:\s*['"]db-/i.test(context)) {
|
|
1493
|
+
return true;
|
|
1494
|
+
}
|
|
1495
|
+
}
|
|
1496
|
+
// FALSE POSITIVE: Comments (not actual code)
|
|
1497
|
+
if (/^\/\/|^\/\*|\*\/\s*$/.test(evidence.trim())) {
|
|
1498
|
+
return true;
|
|
1499
|
+
}
|
|
1500
|
+
// FALSE POSITIVE: String literals in documentation/messages
|
|
1501
|
+
if (/(?:title|description|message|error|text|comment)\s*:\s*['"`].*(?:find|query|SELECT)/i.test(evidence)) {
|
|
1502
|
+
return true;
|
|
1503
|
+
}
|
|
1504
|
+
// FALSE POSITIVE: JSDoc or code comments
|
|
1505
|
+
if (/\/\*\*[\s\S]*?\*\/|@example|@description|\/\/\s*(?:Example|Note|TODO)/i.test(context)) {
|
|
1506
|
+
return true;
|
|
1507
|
+
}
|
|
1508
|
+
// ============================================
|
|
1509
|
+
// LAYER 2: JavaScript Array Methods (NOT Database Queries)
|
|
1510
|
+
// ============================================
|
|
1511
|
+
// FALSE POSITIVE: Array.find() - in-memory operation
|
|
1512
|
+
// Examples: users.find(u => u.id === id), items.find(item => ...)
|
|
1513
|
+
if (/\.find\s*\(/i.test(evidence)) {
|
|
1514
|
+
// Check if it's on an array variable (not a database model)
|
|
1515
|
+
if (/(?:const|let|var|return)\s+\w+\s*=\s*\w+\.find\(/i.test(evidence)) {
|
|
1516
|
+
return true;
|
|
1517
|
+
}
|
|
1518
|
+
// Check for common array variable names
|
|
1519
|
+
if (/(?:array|list|items|results|data|collection|records|rows|entries|elements)\.find\(/i.test(evidence)) {
|
|
1520
|
+
return true;
|
|
1521
|
+
}
|
|
1522
|
+
// Check if the source is clearly an array
|
|
1523
|
+
if (/\[\s*.*\s*\]\.find\(|\.filter\([^)]*\)\.find\(|\.map\([^)]*\)\.find\(/i.test(context)) {
|
|
1524
|
+
return true;
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
// FALSE POSITIVE: Array.filter() - in-memory operation
|
|
1528
|
+
if (/\.filter\s*\(/i.test(evidence)) {
|
|
1529
|
+
return true; // filter() is always an array method, never a database query
|
|
1530
|
+
}
|
|
1531
|
+
// FALSE POSITIVE: Array.map(), .reduce(), .some(), .every() - all in-memory
|
|
1532
|
+
if (/\.(?:map|reduce|some|every|forEach|slice|splice)\s*\(/i.test(evidence)) {
|
|
1533
|
+
return true;
|
|
1534
|
+
}
|
|
1535
|
+
// ============================================
|
|
1536
|
+
// LAYER 3: Database Query Detection (Real Queries)
|
|
1537
|
+
// ============================================
|
|
1538
|
+
// REAL QUERY: ORM/Query Builder methods
|
|
1539
|
+
const isRealDatabaseQuery =
|
|
1540
|
+
// Prisma
|
|
1541
|
+
/prisma\.\w+\.(?:findMany|findFirst|findUnique|create|update|delete|count)\s*\(/i.test(context) ||
|
|
1542
|
+
// Mongoose
|
|
1543
|
+
/Model\.(?:find|findOne|findById|create|update|delete|count)\s*\(/i.test(context) ||
|
|
1544
|
+
// Sequelize
|
|
1545
|
+
/\.(?:findAll|findOne|findByPk|create|update|destroy)\s*\(/i.test(context) ||
|
|
1546
|
+
// TypeORM
|
|
1547
|
+
/repository\.(?:find|findOne|findAndCount|save|remove)\s*\(/i.test(context) ||
|
|
1548
|
+
// Knex
|
|
1549
|
+
/knex\s*\(\s*['"`]\w+['"`]\s*\)\.(?:select|where|insert|update|delete)/i.test(context) ||
|
|
1550
|
+
// Raw SQL
|
|
1551
|
+
/(?:execute|query|raw)\s*\(\s*['"`](?:SELECT|INSERT|UPDATE|DELETE)/i.test(context);
|
|
1552
|
+
if (!isRealDatabaseQuery) {
|
|
1553
|
+
// Not a database query at all
|
|
1554
|
+
return true;
|
|
1555
|
+
}
|
|
1556
|
+
// ============================================
|
|
1557
|
+
// LAYER 4: Valid Query Patterns (With Limits/Pagination)
|
|
1558
|
+
// ============================================
|
|
1559
|
+
// VALID: Query has LIMIT/TAKE/TOP
|
|
1560
|
+
if (/\.(?:limit|take|top|first)\s*\(\s*\d+\s*\)/i.test(context)) {
|
|
1561
|
+
return true;
|
|
1562
|
+
}
|
|
1563
|
+
// VALID: Query has pagination (skip/offset + limit/take)
|
|
1564
|
+
if (/\.(?:skip|offset)\s*\([^)]*\)[\s\S]{0,100}\.(?:limit|take)\s*\(/i.test(context)) {
|
|
1565
|
+
return true;
|
|
1566
|
+
}
|
|
1567
|
+
// VALID: Query has WHERE clause with specific ID/unique field
|
|
1568
|
+
if (/\.(?:where|findUnique|findById|findByPk)\s*\(\s*\{[^}]*(?:id|_id|uuid|key)\s*:/i.test(context)) {
|
|
1569
|
+
return true;
|
|
1570
|
+
}
|
|
1571
|
+
// VALID: Query uses findOne/findFirst (returns single record)
|
|
1572
|
+
if (/\.(?:findOne|findFirst|findUnique|findById|findByPk)\s*\(/i.test(context)) {
|
|
1573
|
+
return true;
|
|
1574
|
+
}
|
|
1575
|
+
// VALID: Count queries (don't return data)
|
|
1576
|
+
if (/\.count\s*\(/i.test(context)) {
|
|
1577
|
+
return true;
|
|
1578
|
+
}
|
|
1579
|
+
// VALID: Aggregation queries (usually return summary data)
|
|
1580
|
+
if (/\.(?:aggregate|groupBy|sum|avg|min|max)\s*\(/i.test(context)) {
|
|
1581
|
+
return true;
|
|
1582
|
+
}
|
|
1583
|
+
// ============================================
|
|
1584
|
+
// LAYER 5: Context-Specific Valid Patterns
|
|
1585
|
+
// ============================================
|
|
1586
|
+
// VALID: Small/test datasets (development/testing)
|
|
1587
|
+
if (/\/(?:test|spec|mock|fixture|seed|sample)\//i.test(filePath)) {
|
|
1588
|
+
return true;
|
|
1589
|
+
}
|
|
1590
|
+
// VALID: Admin/internal tools (not user-facing)
|
|
1591
|
+
if (/\/(?:admin|internal|tools|scripts|migrations)\//i.test(filePath)) {
|
|
1592
|
+
return true;
|
|
1593
|
+
}
|
|
1594
|
+
// VALID: Background jobs/workers (controlled execution)
|
|
1595
|
+
if (/\/(?:jobs|workers|tasks|cron|queue)\//i.test(filePath)) {
|
|
1596
|
+
return true;
|
|
1597
|
+
}
|
|
1598
|
+
// VALID: Queries with explicit small limits in code
|
|
1599
|
+
if (/(?:MAX|LIMIT|TOP)_(?:RESULTS|ROWS|ITEMS)\s*=\s*\d{1,3}\b/i.test(context)) {
|
|
1600
|
+
return true;
|
|
1601
|
+
}
|
|
1602
|
+
// ============================================
|
|
1603
|
+
// LAYER 6: Framework-Specific Patterns
|
|
1604
|
+
// ============================================
|
|
1605
|
+
// VALID: Next.js with pagination params
|
|
1606
|
+
if (/searchParams|params\.page|params\.limit|query\.page|query\.limit/i.test(context)) {
|
|
1607
|
+
return true;
|
|
1608
|
+
}
|
|
1609
|
+
// VALID: GraphQL resolvers (framework handles pagination)
|
|
1610
|
+
if (/resolver|@Query|@Mutation|GraphQL/i.test(context)) {
|
|
1611
|
+
return true;
|
|
1612
|
+
}
|
|
1613
|
+
// VALID: tRPC procedures (framework handles pagination)
|
|
1614
|
+
if (/\.query\(|\.mutation\(|trpc\./i.test(context)) {
|
|
1615
|
+
return true;
|
|
1616
|
+
}
|
|
1617
|
+
// ============================================
|
|
1618
|
+
// LAYER 7: Pattern Definitions (Not Real Code)
|
|
1619
|
+
// ============================================
|
|
1620
|
+
// FALSE POSITIVE: Regex patterns containing "find(" or "SELECT *"
|
|
1621
|
+
if (/\/.*(?:find|SELECT|query).*\/[gimuy]*/i.test(evidence)) {
|
|
1622
|
+
return true;
|
|
1623
|
+
}
|
|
1624
|
+
// FALSE POSITIVE: Pattern object definitions
|
|
1625
|
+
if (/\{\s*id\s*:\s*['"]|regex\s*:\s*\/|pattern\s*:\s*\//i.test(context)) {
|
|
1626
|
+
return true;
|
|
1627
|
+
}
|
|
1628
|
+
// ============================================
|
|
1629
|
+
// LAYER 8: Real Issues (Return false to flag)
|
|
1630
|
+
// ============================================
|
|
1631
|
+
// REAL ISSUE: findMany() or find({}) without any limits
|
|
1632
|
+
if (/\.(?:findMany|find)\s*\(\s*\{?\s*\}?\s*\)/i.test(evidence)) {
|
|
1633
|
+
// Check if there's NO limit anywhere in the context
|
|
1634
|
+
const hasNoLimit = !/\.(?:limit|take|top|first|skip|offset|page)\s*\(/i.test(context);
|
|
1635
|
+
if (hasNoLimit) {
|
|
1636
|
+
return false; // This is a REAL issue
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
// REAL ISSUE: SELECT * without LIMIT
|
|
1640
|
+
if (/SELECT\s+\*\s+FROM/i.test(evidence)) {
|
|
1641
|
+
const hasNoLimit = !/LIMIT\s+\d+|TOP\s+\d+|FETCH\s+FIRST/i.test(context);
|
|
1642
|
+
if (hasNoLimit) {
|
|
1643
|
+
return false; // This is a REAL issue
|
|
1644
|
+
}
|
|
1645
|
+
}
|
|
1646
|
+
// Default: Assume it's safe (conservative approach)
|
|
1647
|
+
return true;
|
|
1648
|
+
}
|
|
1649
|
+
function validatePromiseAllErrorHandling(evidence, context) {
|
|
1650
|
+
// FALSE POSITIVE: Wrapped in try-catch block
|
|
1651
|
+
if (/try\s*\{[\s\S]*Promise\.all[\s\S]*\}\s*catch/i.test(context)) {
|
|
1652
|
+
return true;
|
|
1653
|
+
}
|
|
1654
|
+
// FALSE POSITIVE: Error handling in the calling function
|
|
1655
|
+
if (/catch\s*\([^)]*error/i.test(context)) {
|
|
1656
|
+
return true;
|
|
1657
|
+
}
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
function validateMagicNumbers(evidence, context) {
|
|
1661
|
+
// HTTP status codes
|
|
1662
|
+
if (/\b(?:200|201|204|400|401|403|404|500|503)\b/.test(evidence))
|
|
1663
|
+
return true;
|
|
1664
|
+
// Common constants
|
|
1665
|
+
if (/\b(?:1000|1024|60|24|365)\b/.test(evidence))
|
|
1666
|
+
return true;
|
|
1667
|
+
return false;
|
|
1668
|
+
}
|
|
1669
|
+
function validateAnyType(evidence, context) {
|
|
1670
|
+
// Intentional any for error handling
|
|
1671
|
+
return /catch.*any|error.*any|unknown.*any/i.test(context);
|
|
1672
|
+
}
|
|
1673
|
+
function validateTodoComment(evidence, context) {
|
|
1674
|
+
// Not actual TODO, just explanation
|
|
1675
|
+
return /\/\/.*example|\/\/.*note|\/\/.*explanation/i.test(evidence);
|
|
1676
|
+
}
|
|
1677
|
+
function shouldSendToAI(finding) {
|
|
1678
|
+
// Only send low-confidence findings to AI for verification
|
|
1679
|
+
// High-confidence findings are already accurate
|
|
1680
|
+
return finding.confidence === "low" || finding.confidence === "medium";
|
|
1681
|
+
}
|