@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.
Files changed (153) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +17 -0
  3. package/dist/engines/config.d.ts +3 -0
  4. package/dist/engines/config.d.ts.map +1 -0
  5. package/dist/engines/config.js +433 -0
  6. package/dist/engines/config.js.map +1 -0
  7. package/dist/engines/dast.d.ts +3 -0
  8. package/dist/engines/dast.d.ts.map +1 -0
  9. package/dist/engines/dast.js +167 -0
  10. package/dist/engines/dast.js.map +1 -0
  11. package/dist/engines/db-rules.d.ts +3 -0
  12. package/dist/engines/db-rules.d.ts.map +1 -0
  13. package/dist/engines/db-rules.js +166 -0
  14. package/dist/engines/db-rules.js.map +1 -0
  15. package/dist/engines/dependencies.d.ts +3 -0
  16. package/dist/engines/dependencies.d.ts.map +1 -0
  17. package/dist/engines/dependencies.js +167 -0
  18. package/dist/engines/dependencies.js.map +1 -0
  19. package/dist/engines/github-actions.d.ts +3 -0
  20. package/dist/engines/github-actions.d.ts.map +1 -0
  21. package/dist/engines/github-actions.js +215 -0
  22. package/dist/engines/github-actions.js.map +1 -0
  23. package/dist/engines/gitleaks.d.ts +3 -0
  24. package/dist/engines/gitleaks.d.ts.map +1 -0
  25. package/dist/engines/gitleaks.js +107 -0
  26. package/dist/engines/gitleaks.js.map +1 -0
  27. package/dist/engines/npm-audit.d.ts +3 -0
  28. package/dist/engines/npm-audit.d.ts.map +1 -0
  29. package/dist/engines/npm-audit.js +113 -0
  30. package/dist/engines/npm-audit.js.map +1 -0
  31. package/dist/engines/patterns.d.ts +3 -0
  32. package/dist/engines/patterns.d.ts.map +1 -0
  33. package/dist/engines/patterns.js +1330 -0
  34. package/dist/engines/patterns.js.map +1 -0
  35. package/dist/engines/secrets.d.ts +4 -0
  36. package/dist/engines/secrets.d.ts.map +1 -0
  37. package/dist/engines/secrets.js +260 -0
  38. package/dist/engines/secrets.js.map +1 -0
  39. package/dist/engines/semgrep.d.ts +3 -0
  40. package/dist/engines/semgrep.d.ts.map +1 -0
  41. package/dist/engines/semgrep.js +173 -0
  42. package/dist/engines/semgrep.js.map +1 -0
  43. package/dist/engines/types.d.ts +30 -0
  44. package/dist/engines/types.d.ts.map +1 -0
  45. package/dist/engines/types.js +3 -0
  46. package/dist/engines/types.js.map +1 -0
  47. package/dist/engines/url-scanner.d.ts +3 -0
  48. package/dist/engines/url-scanner.d.ts.map +1 -0
  49. package/dist/engines/url-scanner.js +469 -0
  50. package/dist/engines/url-scanner.js.map +1 -0
  51. package/dist/index.d.ts +8 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +8 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/orchestrator.d.ts +32 -0
  56. package/dist/orchestrator.d.ts.map +1 -0
  57. package/dist/orchestrator.js +237 -0
  58. package/dist/orchestrator.js.map +1 -0
  59. package/dist/rules/.gitkeep +0 -0
  60. package/dist/rules/ai-llm-security.yaml +199 -0
  61. package/dist/rules/api-security.yaml +489 -0
  62. package/dist/rules/auth-patterns.yaml +406 -0
  63. package/dist/rules/crypto-patterns.yaml +227 -0
  64. package/dist/rules/database-cloud-security.yaml +169 -0
  65. package/dist/rules/general-security.yaml +588 -0
  66. package/dist/rules/injection-patterns.yaml +318 -0
  67. package/dist/rules/nextjs-security.yaml +532 -0
  68. package/dist/rules/node-security.yaml +380 -0
  69. package/dist/rules/rules/.gitkeep +0 -0
  70. package/dist/rules/rules/ai-llm-security.yaml +199 -0
  71. package/dist/rules/rules/api-security.yaml +489 -0
  72. package/dist/rules/rules/auth-patterns.yaml +406 -0
  73. package/dist/rules/rules/crypto-patterns.yaml +227 -0
  74. package/dist/rules/rules/database-cloud-security.yaml +169 -0
  75. package/dist/rules/rules/general-security.yaml +588 -0
  76. package/dist/rules/rules/injection-patterns.yaml +318 -0
  77. package/dist/rules/rules/nextjs-security.yaml +532 -0
  78. package/dist/rules/rules/node-security.yaml +380 -0
  79. package/dist/rules/rules/supabase-security.yaml +387 -0
  80. package/dist/rules/supabase-security.yaml +387 -0
  81. package/dist/scanning/agents/csharp-agent.d.ts +12 -0
  82. package/dist/scanning/agents/csharp-agent.d.ts.map +1 -0
  83. package/dist/scanning/agents/csharp-agent.js +592 -0
  84. package/dist/scanning/agents/csharp-agent.js.map +1 -0
  85. package/dist/scanning/agents/go-agent.d.ts +14 -0
  86. package/dist/scanning/agents/go-agent.d.ts.map +1 -0
  87. package/dist/scanning/agents/go-agent.js +641 -0
  88. package/dist/scanning/agents/go-agent.js.map +1 -0
  89. package/dist/scanning/agents/java-agent.d.ts +8 -0
  90. package/dist/scanning/agents/java-agent.d.ts.map +1 -0
  91. package/dist/scanning/agents/java-agent.js +807 -0
  92. package/dist/scanning/agents/java-agent.js.map +1 -0
  93. package/dist/scanning/agents/javascript-agent.d.ts +8 -0
  94. package/dist/scanning/agents/javascript-agent.d.ts.map +1 -0
  95. package/dist/scanning/agents/javascript-agent.js +77 -0
  96. package/dist/scanning/agents/javascript-agent.js.map +1 -0
  97. package/dist/scanning/agents/mobile-agent.d.ts +8 -0
  98. package/dist/scanning/agents/mobile-agent.d.ts.map +1 -0
  99. package/dist/scanning/agents/mobile-agent.js +916 -0
  100. package/dist/scanning/agents/mobile-agent.js.map +1 -0
  101. package/dist/scanning/agents/php-agent.d.ts +8 -0
  102. package/dist/scanning/agents/php-agent.d.ts.map +1 -0
  103. package/dist/scanning/agents/php-agent.js +679 -0
  104. package/dist/scanning/agents/php-agent.js.map +1 -0
  105. package/dist/scanning/agents/python-agent.d.ts +8 -0
  106. package/dist/scanning/agents/python-agent.d.ts.map +1 -0
  107. package/dist/scanning/agents/python-agent.js +701 -0
  108. package/dist/scanning/agents/python-agent.js.map +1 -0
  109. package/dist/scanning/agents/supabase-agent.d.ts +8 -0
  110. package/dist/scanning/agents/supabase-agent.d.ts.map +1 -0
  111. package/dist/scanning/agents/supabase-agent.js +484 -0
  112. package/dist/scanning/agents/supabase-agent.js.map +1 -0
  113. package/dist/scanning/orchestrator.d.ts +29 -0
  114. package/dist/scanning/orchestrator.d.ts.map +1 -0
  115. package/dist/scanning/orchestrator.js +186 -0
  116. package/dist/scanning/orchestrator.js.map +1 -0
  117. package/dist/scanning/tech-detector.d.ts +12 -0
  118. package/dist/scanning/tech-detector.d.ts.map +1 -0
  119. package/dist/scanning/tech-detector.js +252 -0
  120. package/dist/scanning/tech-detector.js.map +1 -0
  121. package/dist/scanning/types.d.ts +87 -0
  122. package/dist/scanning/types.d.ts.map +1 -0
  123. package/dist/scanning/types.js +5 -0
  124. package/dist/scanning/types.js.map +1 -0
  125. package/dist/utils/child-process.d.ts +2 -0
  126. package/dist/utils/child-process.d.ts.map +1 -0
  127. package/dist/utils/child-process.js +4 -0
  128. package/dist/utils/child-process.js.map +1 -0
  129. package/dist/utils/context-classifier.d.ts +11 -0
  130. package/dist/utils/context-classifier.d.ts.map +1 -0
  131. package/dist/utils/context-classifier.js +79 -0
  132. package/dist/utils/context-classifier.js.map +1 -0
  133. package/dist/utils/exec.d.ts +18 -0
  134. package/dist/utils/exec.d.ts.map +1 -0
  135. package/dist/utils/exec.js +51 -0
  136. package/dist/utils/exec.js.map +1 -0
  137. package/dist/utils/file-filter.d.ts +4 -0
  138. package/dist/utils/file-filter.d.ts.map +1 -0
  139. package/dist/utils/file-filter.js +68 -0
  140. package/dist/utils/file-filter.js.map +1 -0
  141. package/dist/utils/fs.d.ts +2 -0
  142. package/dist/utils/fs.d.ts.map +1 -0
  143. package/dist/utils/fs.js +5 -0
  144. package/dist/utils/fs.js.map +1 -0
  145. package/dist/utils/logger.d.ts +12 -0
  146. package/dist/utils/logger.d.ts.map +1 -0
  147. package/dist/utils/logger.js +27 -0
  148. package/dist/utils/logger.js.map +1 -0
  149. package/dist/utils/post-processor.d.ts +10 -0
  150. package/dist/utils/post-processor.d.ts.map +1 -0
  151. package/dist/utils/post-processor.js +183 -0
  152. package/dist/utils/post-processor.js.map +1 -0
  153. 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