@oculum/scanner 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.
Files changed (281) hide show
  1. package/dist/formatters/cli-terminal.d.ts +27 -0
  2. package/dist/formatters/cli-terminal.d.ts.map +1 -0
  3. package/dist/formatters/cli-terminal.js +412 -0
  4. package/dist/formatters/cli-terminal.js.map +1 -0
  5. package/dist/formatters/github-comment.d.ts +41 -0
  6. package/dist/formatters/github-comment.d.ts.map +1 -0
  7. package/dist/formatters/github-comment.js +306 -0
  8. package/dist/formatters/github-comment.js.map +1 -0
  9. package/dist/formatters/grouping.d.ts +52 -0
  10. package/dist/formatters/grouping.d.ts.map +1 -0
  11. package/dist/formatters/grouping.js +152 -0
  12. package/dist/formatters/grouping.js.map +1 -0
  13. package/dist/formatters/index.d.ts +9 -0
  14. package/dist/formatters/index.d.ts.map +1 -0
  15. package/dist/formatters/index.js +35 -0
  16. package/dist/formatters/index.js.map +1 -0
  17. package/dist/formatters/vscode-diagnostic.d.ts +103 -0
  18. package/dist/formatters/vscode-diagnostic.d.ts.map +1 -0
  19. package/dist/formatters/vscode-diagnostic.js +151 -0
  20. package/dist/formatters/vscode-diagnostic.js.map +1 -0
  21. package/dist/index.d.ts +52 -0
  22. package/dist/index.d.ts.map +1 -0
  23. package/dist/index.js +648 -0
  24. package/dist/index.js.map +1 -0
  25. package/dist/layer1/comments.d.ts +8 -0
  26. package/dist/layer1/comments.d.ts.map +1 -0
  27. package/dist/layer1/comments.js +203 -0
  28. package/dist/layer1/comments.js.map +1 -0
  29. package/dist/layer1/config-audit.d.ts +8 -0
  30. package/dist/layer1/config-audit.d.ts.map +1 -0
  31. package/dist/layer1/config-audit.js +252 -0
  32. package/dist/layer1/config-audit.js.map +1 -0
  33. package/dist/layer1/entropy.d.ts +8 -0
  34. package/dist/layer1/entropy.d.ts.map +1 -0
  35. package/dist/layer1/entropy.js +500 -0
  36. package/dist/layer1/entropy.js.map +1 -0
  37. package/dist/layer1/file-flags.d.ts +7 -0
  38. package/dist/layer1/file-flags.d.ts.map +1 -0
  39. package/dist/layer1/file-flags.js +112 -0
  40. package/dist/layer1/file-flags.js.map +1 -0
  41. package/dist/layer1/index.d.ts +36 -0
  42. package/dist/layer1/index.d.ts.map +1 -0
  43. package/dist/layer1/index.js +132 -0
  44. package/dist/layer1/index.js.map +1 -0
  45. package/dist/layer1/patterns.d.ts +8 -0
  46. package/dist/layer1/patterns.d.ts.map +1 -0
  47. package/dist/layer1/patterns.js +482 -0
  48. package/dist/layer1/patterns.js.map +1 -0
  49. package/dist/layer1/urls.d.ts +8 -0
  50. package/dist/layer1/urls.d.ts.map +1 -0
  51. package/dist/layer1/urls.js +296 -0
  52. package/dist/layer1/urls.js.map +1 -0
  53. package/dist/layer1/weak-crypto.d.ts +7 -0
  54. package/dist/layer1/weak-crypto.d.ts.map +1 -0
  55. package/dist/layer1/weak-crypto.js +291 -0
  56. package/dist/layer1/weak-crypto.js.map +1 -0
  57. package/dist/layer2/ai-agent-tools.d.ts +19 -0
  58. package/dist/layer2/ai-agent-tools.d.ts.map +1 -0
  59. package/dist/layer2/ai-agent-tools.js +528 -0
  60. package/dist/layer2/ai-agent-tools.js.map +1 -0
  61. package/dist/layer2/ai-endpoint-protection.d.ts +36 -0
  62. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -0
  63. package/dist/layer2/ai-endpoint-protection.js +332 -0
  64. package/dist/layer2/ai-endpoint-protection.js.map +1 -0
  65. package/dist/layer2/ai-execution-sinks.d.ts +18 -0
  66. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -0
  67. package/dist/layer2/ai-execution-sinks.js +496 -0
  68. package/dist/layer2/ai-execution-sinks.js.map +1 -0
  69. package/dist/layer2/ai-fingerprinting.d.ts +7 -0
  70. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -0
  71. package/dist/layer2/ai-fingerprinting.js +654 -0
  72. package/dist/layer2/ai-fingerprinting.js.map +1 -0
  73. package/dist/layer2/ai-prompt-hygiene.d.ts +19 -0
  74. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -0
  75. package/dist/layer2/ai-prompt-hygiene.js +356 -0
  76. package/dist/layer2/ai-prompt-hygiene.js.map +1 -0
  77. package/dist/layer2/ai-rag-safety.d.ts +21 -0
  78. package/dist/layer2/ai-rag-safety.d.ts.map +1 -0
  79. package/dist/layer2/ai-rag-safety.js +459 -0
  80. package/dist/layer2/ai-rag-safety.js.map +1 -0
  81. package/dist/layer2/ai-schema-validation.d.ts +25 -0
  82. package/dist/layer2/ai-schema-validation.d.ts.map +1 -0
  83. package/dist/layer2/ai-schema-validation.js +375 -0
  84. package/dist/layer2/ai-schema-validation.js.map +1 -0
  85. package/dist/layer2/auth-antipatterns.d.ts +20 -0
  86. package/dist/layer2/auth-antipatterns.d.ts.map +1 -0
  87. package/dist/layer2/auth-antipatterns.js +333 -0
  88. package/dist/layer2/auth-antipatterns.js.map +1 -0
  89. package/dist/layer2/byok-patterns.d.ts +12 -0
  90. package/dist/layer2/byok-patterns.d.ts.map +1 -0
  91. package/dist/layer2/byok-patterns.js +299 -0
  92. package/dist/layer2/byok-patterns.js.map +1 -0
  93. package/dist/layer2/dangerous-functions.d.ts +7 -0
  94. package/dist/layer2/dangerous-functions.d.ts.map +1 -0
  95. package/dist/layer2/dangerous-functions.js +1375 -0
  96. package/dist/layer2/dangerous-functions.js.map +1 -0
  97. package/dist/layer2/data-exposure.d.ts +16 -0
  98. package/dist/layer2/data-exposure.d.ts.map +1 -0
  99. package/dist/layer2/data-exposure.js +279 -0
  100. package/dist/layer2/data-exposure.js.map +1 -0
  101. package/dist/layer2/framework-checks.d.ts +7 -0
  102. package/dist/layer2/framework-checks.d.ts.map +1 -0
  103. package/dist/layer2/framework-checks.js +388 -0
  104. package/dist/layer2/framework-checks.js.map +1 -0
  105. package/dist/layer2/index.d.ts +58 -0
  106. package/dist/layer2/index.d.ts.map +1 -0
  107. package/dist/layer2/index.js +380 -0
  108. package/dist/layer2/index.js.map +1 -0
  109. package/dist/layer2/logic-gates.d.ts +7 -0
  110. package/dist/layer2/logic-gates.d.ts.map +1 -0
  111. package/dist/layer2/logic-gates.js +182 -0
  112. package/dist/layer2/logic-gates.js.map +1 -0
  113. package/dist/layer2/risky-imports.d.ts +7 -0
  114. package/dist/layer2/risky-imports.d.ts.map +1 -0
  115. package/dist/layer2/risky-imports.js +161 -0
  116. package/dist/layer2/risky-imports.js.map +1 -0
  117. package/dist/layer2/variables.d.ts +8 -0
  118. package/dist/layer2/variables.d.ts.map +1 -0
  119. package/dist/layer2/variables.js +152 -0
  120. package/dist/layer2/variables.js.map +1 -0
  121. package/dist/layer3/anthropic.d.ts +83 -0
  122. package/dist/layer3/anthropic.d.ts.map +1 -0
  123. package/dist/layer3/anthropic.js +1745 -0
  124. package/dist/layer3/anthropic.js.map +1 -0
  125. package/dist/layer3/index.d.ts +24 -0
  126. package/dist/layer3/index.d.ts.map +1 -0
  127. package/dist/layer3/index.js +119 -0
  128. package/dist/layer3/index.js.map +1 -0
  129. package/dist/layer3/openai.d.ts +25 -0
  130. package/dist/layer3/openai.d.ts.map +1 -0
  131. package/dist/layer3/openai.js +238 -0
  132. package/dist/layer3/openai.js.map +1 -0
  133. package/dist/layer3/package-check.d.ts +63 -0
  134. package/dist/layer3/package-check.d.ts.map +1 -0
  135. package/dist/layer3/package-check.js +508 -0
  136. package/dist/layer3/package-check.js.map +1 -0
  137. package/dist/modes/incremental.d.ts +66 -0
  138. package/dist/modes/incremental.d.ts.map +1 -0
  139. package/dist/modes/incremental.js +200 -0
  140. package/dist/modes/incremental.js.map +1 -0
  141. package/dist/tiers.d.ts +125 -0
  142. package/dist/tiers.d.ts.map +1 -0
  143. package/dist/tiers.js +234 -0
  144. package/dist/tiers.js.map +1 -0
  145. package/dist/types.d.ts +175 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +50 -0
  148. package/dist/types.js.map +1 -0
  149. package/dist/utils/auth-helper-detector.d.ts +56 -0
  150. package/dist/utils/auth-helper-detector.d.ts.map +1 -0
  151. package/dist/utils/auth-helper-detector.js +360 -0
  152. package/dist/utils/auth-helper-detector.js.map +1 -0
  153. package/dist/utils/context-helpers.d.ts +96 -0
  154. package/dist/utils/context-helpers.d.ts.map +1 -0
  155. package/dist/utils/context-helpers.js +493 -0
  156. package/dist/utils/context-helpers.js.map +1 -0
  157. package/dist/utils/diff-detector.d.ts +53 -0
  158. package/dist/utils/diff-detector.d.ts.map +1 -0
  159. package/dist/utils/diff-detector.js +104 -0
  160. package/dist/utils/diff-detector.js.map +1 -0
  161. package/dist/utils/diff-parser.d.ts +80 -0
  162. package/dist/utils/diff-parser.d.ts.map +1 -0
  163. package/dist/utils/diff-parser.js +202 -0
  164. package/dist/utils/diff-parser.js.map +1 -0
  165. package/dist/utils/imported-auth-detector.d.ts +37 -0
  166. package/dist/utils/imported-auth-detector.d.ts.map +1 -0
  167. package/dist/utils/imported-auth-detector.js +251 -0
  168. package/dist/utils/imported-auth-detector.js.map +1 -0
  169. package/dist/utils/middleware-detector.d.ts +55 -0
  170. package/dist/utils/middleware-detector.d.ts.map +1 -0
  171. package/dist/utils/middleware-detector.js +260 -0
  172. package/dist/utils/middleware-detector.js.map +1 -0
  173. package/dist/utils/oauth-flow-detector.d.ts +41 -0
  174. package/dist/utils/oauth-flow-detector.d.ts.map +1 -0
  175. package/dist/utils/oauth-flow-detector.js +202 -0
  176. package/dist/utils/oauth-flow-detector.js.map +1 -0
  177. package/dist/utils/path-exclusions.d.ts +55 -0
  178. package/dist/utils/path-exclusions.d.ts.map +1 -0
  179. package/dist/utils/path-exclusions.js +222 -0
  180. package/dist/utils/path-exclusions.js.map +1 -0
  181. package/dist/utils/project-context-builder.d.ts +119 -0
  182. package/dist/utils/project-context-builder.d.ts.map +1 -0
  183. package/dist/utils/project-context-builder.js +534 -0
  184. package/dist/utils/project-context-builder.js.map +1 -0
  185. package/dist/utils/registry-clients.d.ts +93 -0
  186. package/dist/utils/registry-clients.d.ts.map +1 -0
  187. package/dist/utils/registry-clients.js +273 -0
  188. package/dist/utils/registry-clients.js.map +1 -0
  189. package/dist/utils/trpc-analyzer.d.ts +78 -0
  190. package/dist/utils/trpc-analyzer.d.ts.map +1 -0
  191. package/dist/utils/trpc-analyzer.js +297 -0
  192. package/dist/utils/trpc-analyzer.js.map +1 -0
  193. package/package.json +45 -0
  194. package/src/__tests__/benchmark/fixtures/false-positives.ts +227 -0
  195. package/src/__tests__/benchmark/fixtures/index.ts +68 -0
  196. package/src/__tests__/benchmark/fixtures/layer1/config-audit.ts +364 -0
  197. package/src/__tests__/benchmark/fixtures/layer1/hardcoded-secrets.ts +173 -0
  198. package/src/__tests__/benchmark/fixtures/layer1/high-entropy.ts +234 -0
  199. package/src/__tests__/benchmark/fixtures/layer1/index.ts +31 -0
  200. package/src/__tests__/benchmark/fixtures/layer1/sensitive-urls.ts +90 -0
  201. package/src/__tests__/benchmark/fixtures/layer1/weak-crypto.ts +197 -0
  202. package/src/__tests__/benchmark/fixtures/layer2/ai-agent-tools.ts +170 -0
  203. package/src/__tests__/benchmark/fixtures/layer2/ai-endpoint-protection.ts +418 -0
  204. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +189 -0
  205. package/src/__tests__/benchmark/fixtures/layer2/ai-fingerprinting.ts +316 -0
  206. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +178 -0
  207. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +184 -0
  208. package/src/__tests__/benchmark/fixtures/layer2/ai-schema-validation.ts +434 -0
  209. package/src/__tests__/benchmark/fixtures/layer2/auth-antipatterns.ts +159 -0
  210. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +112 -0
  211. package/src/__tests__/benchmark/fixtures/layer2/dangerous-functions.ts +246 -0
  212. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +168 -0
  213. package/src/__tests__/benchmark/fixtures/layer2/framework-checks.ts +346 -0
  214. package/src/__tests__/benchmark/fixtures/layer2/index.ts +67 -0
  215. package/src/__tests__/benchmark/fixtures/layer2/injection-vulnerabilities.ts +239 -0
  216. package/src/__tests__/benchmark/fixtures/layer2/logic-gates.ts +246 -0
  217. package/src/__tests__/benchmark/fixtures/layer2/risky-imports.ts +231 -0
  218. package/src/__tests__/benchmark/fixtures/layer2/variables.ts +167 -0
  219. package/src/__tests__/benchmark/index.ts +29 -0
  220. package/src/__tests__/benchmark/run-benchmark.ts +144 -0
  221. package/src/__tests__/benchmark/run-depth-validation.ts +206 -0
  222. package/src/__tests__/benchmark/run-real-world-test.ts +243 -0
  223. package/src/__tests__/benchmark/security-benchmark-script.ts +1737 -0
  224. package/src/__tests__/benchmark/tier-integration-script.ts +177 -0
  225. package/src/__tests__/benchmark/types.ts +144 -0
  226. package/src/__tests__/benchmark/utils/test-runner.ts +475 -0
  227. package/src/__tests__/regression/known-false-positives.test.ts +467 -0
  228. package/src/__tests__/snapshots/__snapshots__/scan-depth.test.ts.snap +178 -0
  229. package/src/__tests__/snapshots/scan-depth.test.ts +258 -0
  230. package/src/__tests__/validation/analyze-results.ts +542 -0
  231. package/src/__tests__/validation/extract-for-triage.ts +146 -0
  232. package/src/__tests__/validation/fp-deep-analysis.ts +327 -0
  233. package/src/__tests__/validation/run-validation.ts +364 -0
  234. package/src/__tests__/validation/triage-template.md +132 -0
  235. package/src/formatters/cli-terminal.ts +446 -0
  236. package/src/formatters/github-comment.ts +382 -0
  237. package/src/formatters/grouping.ts +190 -0
  238. package/src/formatters/index.ts +47 -0
  239. package/src/formatters/vscode-diagnostic.ts +243 -0
  240. package/src/index.ts +823 -0
  241. package/src/layer1/comments.ts +218 -0
  242. package/src/layer1/config-audit.ts +289 -0
  243. package/src/layer1/entropy.ts +583 -0
  244. package/src/layer1/file-flags.ts +127 -0
  245. package/src/layer1/index.ts +181 -0
  246. package/src/layer1/patterns.ts +516 -0
  247. package/src/layer1/urls.ts +334 -0
  248. package/src/layer1/weak-crypto.ts +328 -0
  249. package/src/layer2/ai-agent-tools.ts +601 -0
  250. package/src/layer2/ai-endpoint-protection.ts +387 -0
  251. package/src/layer2/ai-execution-sinks.ts +580 -0
  252. package/src/layer2/ai-fingerprinting.ts +758 -0
  253. package/src/layer2/ai-prompt-hygiene.ts +411 -0
  254. package/src/layer2/ai-rag-safety.ts +511 -0
  255. package/src/layer2/ai-schema-validation.ts +421 -0
  256. package/src/layer2/auth-antipatterns.ts +394 -0
  257. package/src/layer2/byok-patterns.ts +336 -0
  258. package/src/layer2/dangerous-functions.ts +1563 -0
  259. package/src/layer2/data-exposure.ts +315 -0
  260. package/src/layer2/framework-checks.ts +433 -0
  261. package/src/layer2/index.ts +473 -0
  262. package/src/layer2/logic-gates.ts +206 -0
  263. package/src/layer2/risky-imports.ts +186 -0
  264. package/src/layer2/variables.ts +166 -0
  265. package/src/layer3/anthropic.ts +2030 -0
  266. package/src/layer3/index.ts +130 -0
  267. package/src/layer3/package-check.ts +604 -0
  268. package/src/modes/incremental.ts +293 -0
  269. package/src/tiers.ts +318 -0
  270. package/src/types.ts +284 -0
  271. package/src/utils/auth-helper-detector.ts +443 -0
  272. package/src/utils/context-helpers.ts +535 -0
  273. package/src/utils/diff-detector.ts +135 -0
  274. package/src/utils/diff-parser.ts +272 -0
  275. package/src/utils/imported-auth-detector.ts +320 -0
  276. package/src/utils/middleware-detector.ts +333 -0
  277. package/src/utils/oauth-flow-detector.ts +246 -0
  278. package/src/utils/path-exclusions.ts +266 -0
  279. package/src/utils/project-context-builder.ts +707 -0
  280. package/src/utils/registry-clients.ts +351 -0
  281. package/src/utils/trpc-analyzer.ts +382 -0
@@ -0,0 +1,433 @@
1
+ /**
2
+ * Layer 2: Framework-Specific Security Checks
3
+ * Detects security issues specific to popular frameworks (Next.js, Express, React, etc.)
4
+ */
5
+
6
+ import type { Vulnerability, VulnerabilitySeverity } from '../types'
7
+ import {
8
+ isComment,
9
+ isServerOnlyFile,
10
+ isEnvVarReference,
11
+ getServiceRoleKeyContext,
12
+ isTestOrMockFile,
13
+ } from '../utils/context-helpers'
14
+
15
+ interface FrameworkPattern {
16
+ name: string
17
+ pattern: RegExp
18
+ severity: VulnerabilitySeverity
19
+ description: string
20
+ suggestedFix: string
21
+ framework: string
22
+ }
23
+
24
+ const FRAMEWORK_PATTERNS: FrameworkPattern[] = [
25
+ // ==================== Next.js ====================
26
+ {
27
+ name: 'Next.js API route without auth',
28
+ pattern: /export\s+(default\s+)?(async\s+)?function\s+handler\s*\(/gi,
29
+ severity: 'medium',
30
+ description: 'Next.js API route handler - ensure authentication is implemented',
31
+ suggestedFix: 'Add authentication check at the start of the handler',
32
+ framework: 'nextjs',
33
+ },
34
+ {
35
+ name: 'Next.js getServerSideProps without auth',
36
+ pattern: /export\s+(async\s+)?function\s+getServerSideProps/gi,
37
+ severity: 'low',
38
+ description: 'Server-side props function - verify auth for protected pages',
39
+ suggestedFix: 'Check authentication and redirect if needed',
40
+ framework: 'nextjs',
41
+ },
42
+ {
43
+ name: 'Next.js exposed server action',
44
+ pattern: /['"]use server['"]\s*;?\s*\n\s*export\s+(async\s+)?function/gi,
45
+ severity: 'medium',
46
+ description: 'Server action is publicly accessible - ensure proper validation',
47
+ suggestedFix: 'Add authentication and input validation to server actions',
48
+ framework: 'nextjs',
49
+ },
50
+ {
51
+ name: 'Next.js revalidate with user input',
52
+ pattern: /revalidatePath\s*\(\s*(req|request|params|searchParams)/gi,
53
+ severity: 'high',
54
+ description: 'revalidatePath with user input could allow cache poisoning',
55
+ suggestedFix: 'Validate and sanitize paths before revalidation',
56
+ framework: 'nextjs',
57
+ },
58
+ {
59
+ name: 'Next.js redirect with user input',
60
+ pattern: /redirect\s*\(\s*(req|request|params|searchParams|url)/gi,
61
+ severity: 'high',
62
+ description: 'Redirect with user input can lead to open redirect vulnerability',
63
+ suggestedFix: 'Validate redirect URLs against an allowlist',
64
+ framework: 'nextjs',
65
+ },
66
+ {
67
+ name: 'Next.js unstable_cache without tags',
68
+ pattern: /unstable_cache\s*\([^)]*\)(?!.*tags)/gi,
69
+ severity: 'low',
70
+ description: 'unstable_cache without tags makes invalidation difficult',
71
+ suggestedFix: 'Add cache tags for proper cache invalidation',
72
+ framework: 'nextjs',
73
+ },
74
+
75
+ // ==================== Express ====================
76
+ {
77
+ name: 'Express without helmet',
78
+ pattern: /express\s*\(\s*\)(?![\s\S]*helmet)/gi,
79
+ severity: 'medium',
80
+ description: 'Express app may lack security headers (helmet middleware)',
81
+ suggestedFix: 'Add helmet middleware: app.use(helmet())',
82
+ framework: 'express',
83
+ },
84
+ {
85
+ name: 'Express CORS allow all',
86
+ pattern: /cors\s*\(\s*\{[^}]*origin\s*:\s*['"]\*['"]/gi,
87
+ severity: 'high',
88
+ description: 'CORS configured to allow all origins',
89
+ suggestedFix: 'Restrict CORS to specific trusted origins',
90
+ framework: 'express',
91
+ },
92
+ {
93
+ name: 'Express body parser limit',
94
+ pattern: /bodyParser\.(json|urlencoded)\s*\(\s*\)(?!.*limit)/gi,
95
+ severity: 'low',
96
+ description: 'Body parser without size limit can lead to DoS',
97
+ suggestedFix: 'Add limit option: bodyParser.json({ limit: "100kb" })',
98
+ framework: 'express',
99
+ },
100
+ {
101
+ name: 'Express trust proxy',
102
+ pattern: /app\.set\s*\(\s*['"]trust proxy['"]\s*,\s*true\s*\)/gi,
103
+ severity: 'medium',
104
+ description: 'Trust proxy enabled - ensure this is intentional',
105
+ suggestedFix: 'Only enable trust proxy behind a known reverse proxy',
106
+ framework: 'express',
107
+ },
108
+ {
109
+ name: 'Express error handler exposes stack',
110
+ pattern: /res\.(json|send)\s*\(\s*\{[^}]*stack|err\.stack/gi,
111
+ severity: 'high',
112
+ description: 'Error stack trace may be exposed to clients',
113
+ suggestedFix: 'Only expose stack traces in development mode',
114
+ framework: 'express',
115
+ },
116
+
117
+ // ==================== React ====================
118
+ {
119
+ name: 'React useEffect with async',
120
+ pattern: /useEffect\s*\(\s*async\s*\(/gi,
121
+ severity: 'low',
122
+ description: 'useEffect with async function can cause memory leaks',
123
+ suggestedFix: 'Define async function inside useEffect and call it',
124
+ framework: 'react',
125
+ },
126
+ {
127
+ name: 'React state with sensitive data',
128
+ pattern: /useState\s*\(\s*['"]?(password|secret|token|apiKey|api_key)/gi,
129
+ severity: 'medium',
130
+ description: 'Sensitive data in React state may be exposed in dev tools',
131
+ suggestedFix: 'Avoid storing sensitive data in client-side state',
132
+ framework: 'react',
133
+ },
134
+ {
135
+ name: 'React href javascript:',
136
+ pattern: /href\s*=\s*[{'"]*javascript:/gi,
137
+ severity: 'high',
138
+ description: 'javascript: URLs can lead to XSS vulnerabilities',
139
+ suggestedFix: 'Use onClick handlers instead of javascript: URLs',
140
+ framework: 'react',
141
+ },
142
+ {
143
+ name: 'React uncontrolled input with defaultValue',
144
+ pattern: /defaultValue\s*=\s*\{[^}]*(password|secret|token)/gi,
145
+ severity: 'medium',
146
+ description: 'Sensitive data in defaultValue may persist in DOM',
147
+ suggestedFix: 'Use controlled inputs for sensitive data',
148
+ framework: 'react',
149
+ },
150
+
151
+ // ==================== Vue ====================
152
+ {
153
+ name: 'Vue v-html directive',
154
+ pattern: /v-html\s*=\s*['"]/gi,
155
+ severity: 'high',
156
+ description: 'v-html can lead to XSS if content is not sanitized',
157
+ suggestedFix: 'Sanitize HTML content or use v-text for plain text',
158
+ framework: 'vue',
159
+ },
160
+ {
161
+ name: 'Vue computed with side effects',
162
+ pattern: /computed\s*:\s*\{[^}]*\b(fetch|axios|http|api)\b/gi,
163
+ severity: 'low',
164
+ description: 'Computed properties with side effects can cause issues',
165
+ suggestedFix: 'Move API calls to methods or lifecycle hooks',
166
+ framework: 'vue',
167
+ },
168
+
169
+ // ==================== Django ====================
170
+ {
171
+ name: 'Django DEBUG in production',
172
+ pattern: /DEBUG\s*=\s*True/gi,
173
+ severity: 'critical',
174
+ description: 'Django DEBUG mode should be disabled in production',
175
+ suggestedFix: 'Set DEBUG = False in production settings',
176
+ framework: 'django',
177
+ },
178
+ {
179
+ name: 'Django ALLOWED_HOSTS wildcard',
180
+ pattern: /ALLOWED_HOSTS\s*=\s*\[\s*['"]\*['"]/gi,
181
+ severity: 'high',
182
+ description: 'ALLOWED_HOSTS with wildcard allows any host',
183
+ suggestedFix: 'Specify exact allowed hostnames',
184
+ framework: 'django',
185
+ },
186
+ {
187
+ name: 'Django SECRET_KEY hardcoded',
188
+ pattern: /SECRET_KEY\s*=\s*['"][^'"]+['"]/gi,
189
+ severity: 'critical',
190
+ description: 'Django SECRET_KEY should not be hardcoded',
191
+ suggestedFix: 'Load SECRET_KEY from environment variable',
192
+ framework: 'django',
193
+ },
194
+
195
+ // ==================== Flask ====================
196
+ {
197
+ name: 'Flask debug mode',
198
+ pattern: /app\.run\s*\([^)]*debug\s*=\s*True/gi,
199
+ severity: 'critical',
200
+ description: 'Flask debug mode should be disabled in production',
201
+ suggestedFix: 'Set debug=False in production',
202
+ framework: 'flask',
203
+ },
204
+ {
205
+ name: 'Flask secret key hardcoded',
206
+ pattern: /app\.secret_key\s*=\s*['"][^'"]+['"]/gi,
207
+ severity: 'critical',
208
+ description: 'Flask secret_key should not be hardcoded',
209
+ suggestedFix: 'Load secret_key from environment variable',
210
+ framework: 'flask',
211
+ },
212
+
213
+ // ==================== Supabase ====================
214
+ {
215
+ name: 'Supabase service role key exposed',
216
+ pattern: /supabaseServiceRole|service_role|SUPABASE_SERVICE_ROLE/gi,
217
+ severity: 'critical',
218
+ description: 'Supabase service role key should never be exposed to client',
219
+ suggestedFix: 'Only use service role key in server-side code',
220
+ framework: 'supabase',
221
+ },
222
+ {
223
+ name: 'Supabase RLS bypass',
224
+ pattern: /\.rpc\s*\(\s*['"][^'"]+['"]\s*,\s*\{[^}]*\}\s*,\s*\{[^}]*count/gi,
225
+ severity: 'medium',
226
+ description: 'RPC call may bypass Row Level Security',
227
+ suggestedFix: 'Ensure RPC functions have proper security checks',
228
+ framework: 'supabase',
229
+ },
230
+ ]
231
+
232
+ // Detect framework from file content and path
233
+ function detectFramework(content: string, filePath: string): string[] {
234
+ const frameworks: string[] = []
235
+
236
+ // Next.js
237
+ if (content.includes('next/') || content.includes('from "next"') ||
238
+ filePath.includes('/app/') || filePath.includes('/pages/')) {
239
+ frameworks.push('nextjs')
240
+ }
241
+
242
+ // Express
243
+ if (content.includes('express()') || content.includes('from "express"') ||
244
+ content.includes("require('express')")) {
245
+ frameworks.push('express')
246
+ }
247
+
248
+ // React
249
+ if (content.includes('from "react"') || content.includes("from 'react'") ||
250
+ content.includes('useState') || content.includes('useEffect')) {
251
+ frameworks.push('react')
252
+ }
253
+
254
+ // Vue
255
+ if (content.includes('from "vue"') || content.includes('Vue.component') ||
256
+ filePath.endsWith('.vue')) {
257
+ frameworks.push('vue')
258
+ }
259
+
260
+ // Django
261
+ if (content.includes('from django') || content.includes('import django') ||
262
+ filePath.includes('settings.py')) {
263
+ frameworks.push('django')
264
+ }
265
+
266
+ // Flask
267
+ if (content.includes('from flask') || content.includes('Flask(__name__)')) {
268
+ frameworks.push('flask')
269
+ }
270
+
271
+ // Supabase
272
+ if (content.includes('supabase') || content.includes('@supabase/')) {
273
+ frameworks.push('supabase')
274
+ }
275
+
276
+ return frameworks
277
+ }
278
+
279
+ export function detectFrameworkIssues(
280
+ content: string,
281
+ filePath: string
282
+ ): Vulnerability[] {
283
+ const vulnerabilities: Vulnerability[] = []
284
+ const lines = content.split('\n')
285
+ const detectedFrameworks = detectFramework(content, filePath)
286
+ const isTestFile = isTestOrMockFile(filePath)
287
+
288
+ lines.forEach((line, index) => {
289
+ // Skip comment lines
290
+ if (isComment(line)) return
291
+
292
+ for (const pattern of FRAMEWORK_PATTERNS) {
293
+ // Only check patterns for detected frameworks (or check all if none detected)
294
+ if (detectedFrameworks.length > 0 && !detectedFrameworks.includes(pattern.framework)) {
295
+ continue
296
+ }
297
+
298
+ const regex = new RegExp(pattern.pattern.source, pattern.pattern.flags)
299
+
300
+ if (regex.test(line)) {
301
+ // Special handling for Supabase service role key
302
+ if (pattern.name === 'Supabase service role key exposed') {
303
+ // Check if this is a proper Supabase client setup pattern
304
+ // Expected safe pattern:
305
+ // - Variable named supabaseServiceRoleKey (or similar)
306
+ // - Loaded from process.env.SUPABASE_SERVICE_ROLE_KEY
307
+ // - Used in server-only file (lib/supabase/server.ts, /api/, etc.)
308
+
309
+ const isServerFile = isServerOnlyFile(filePath)
310
+ const usesEnvVar = isEnvVarReference(line)
311
+
312
+ // Check if this line is just a variable declaration from env var
313
+ // e.g., const supabaseServiceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
314
+ const isEnvVarDeclaration = /(?:const|let|var)\s+\w*(?:service.*role|supabase.*key)\w*\s*=\s*process\.env\./i.test(line)
315
+
316
+ // Check if this is a createClient call with variable reference (not hardcoded)
317
+ // e.g., createClient(supabaseUrl, supabaseServiceRoleKey, {...})
318
+ const isCreateClientWithVar = /createClient\s*\(\s*\w+\s*,\s*\w*(?:service.*role|key)\w*\s*[,)]/i.test(line)
319
+
320
+ // If server file + env var pattern OR server file + createClient with variable reference, it's safe
321
+ if (isServerFile) {
322
+ if (isEnvVarDeclaration || usesEnvVar) {
323
+ // Safe: declaring/loading from env var in server file
324
+ continue
325
+ }
326
+ if (isCreateClientWithVar && !/"[a-zA-Z0-9._-]{20,}"/.test(line)) {
327
+ // Safe: using variable (not hardcoded string) in createClient
328
+ // Check surrounding content for env var loading
329
+ const fileHasEnvVarLoading = content.includes('process.env.SUPABASE_SERVICE_ROLE_KEY')
330
+ if (fileHasEnvVarLoading) {
331
+ continue
332
+ }
333
+ }
334
+ }
335
+
336
+ const serviceRoleContext = getServiceRoleKeyContext(line, filePath)
337
+
338
+ // Double-check: Server-only file using env var = safe, skip entirely
339
+ if (serviceRoleContext === 'safe_server') {
340
+ continue
341
+ }
342
+
343
+ // Determine severity and whether AI validation is needed
344
+ let adjustedSeverity = pattern.severity
345
+ let adjustedDescription = pattern.description
346
+ let requiresAIValidation = false
347
+ let adjustedConfidence: 'high' | 'medium' | 'low' = 'medium'
348
+
349
+ if (serviceRoleContext === 'client_exposure') {
350
+ // Client exposure = critical
351
+ adjustedSeverity = 'critical'
352
+ adjustedDescription = 'Supabase service role key may be exposed to client - this bypasses RLS'
353
+ requiresAIValidation = true
354
+ adjustedConfidence = 'high'
355
+ } else {
356
+ // needs_review
357
+ if (isEnvVarReference(line)) {
358
+ // Using env var in non-server file - needs review
359
+ adjustedSeverity = 'medium'
360
+ adjustedDescription = 'Supabase service role key usage - verify this is server-only code'
361
+ requiresAIValidation = true
362
+ } else if (isServerOnlyFile(filePath)) {
363
+ // Hardcoded in server file - bad practice but not critical
364
+ adjustedSeverity = 'high'
365
+ adjustedDescription = 'Supabase service role key should use environment variable'
366
+ requiresAIValidation = true
367
+ } else {
368
+ // Unknown context - flag for review
369
+ adjustedSeverity = 'high'
370
+ requiresAIValidation = true
371
+ }
372
+ }
373
+
374
+ // Downgrade test file severity
375
+ if (isTestFile) {
376
+ adjustedSeverity = 'low'
377
+ adjustedConfidence = 'low'
378
+ adjustedDescription = `${adjustedDescription} (in test file)`
379
+ }
380
+
381
+ vulnerabilities.push({
382
+ id: `framework-${filePath}-${index + 1}-${pattern.name}`,
383
+ filePath,
384
+ lineNumber: index + 1,
385
+ lineContent: line.trim(),
386
+ severity: adjustedSeverity,
387
+ category: 'insecure_config',
388
+ title: `[${pattern.framework}] ${pattern.name}`,
389
+ description: adjustedDescription,
390
+ suggestedFix: pattern.suggestedFix,
391
+ confidence: adjustedConfidence,
392
+ layer: 2,
393
+ requiresAIValidation,
394
+ })
395
+ break
396
+ }
397
+
398
+ // Standard handling for other patterns
399
+ let severity = pattern.severity
400
+ let confidence: 'high' | 'medium' | 'low' = 'medium'
401
+
402
+ // Downgrade test file severity
403
+ if (isTestFile) {
404
+ if (severity === 'critical') {
405
+ severity = 'medium'
406
+ } else if (severity === 'high') {
407
+ severity = 'low'
408
+ } else {
409
+ severity = 'info'
410
+ }
411
+ confidence = 'low'
412
+ }
413
+
414
+ vulnerabilities.push({
415
+ id: `framework-${filePath}-${index + 1}-${pattern.name}`,
416
+ filePath,
417
+ lineNumber: index + 1,
418
+ lineContent: line.trim(),
419
+ severity,
420
+ category: 'insecure_config',
421
+ title: `[${pattern.framework}] ${pattern.name}`,
422
+ description: isTestFile ? `${pattern.description} (in test file)` : pattern.description,
423
+ suggestedFix: pattern.suggestedFix,
424
+ confidence,
425
+ layer: 2,
426
+ })
427
+ break // Only report once per line
428
+ }
429
+ }
430
+ })
431
+
432
+ return vulnerabilities
433
+ }