@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,1375 @@
1
+ "use strict";
2
+ /**
3
+ * Layer 2: Dangerous Function Call Analysis
4
+ * Detects usage of dangerous functions that can lead to security vulnerabilities
5
+ */
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ exports.detectDangerousFunctions = detectDangerousFunctions;
8
+ const context_helpers_1 = require("../utils/context-helpers");
9
+ /**
10
+ * Check if exec() call is from child_process (dangerous) vs RegExp.exec (safe)
11
+ * Returns true if this is a child_process exec call that should be flagged
12
+ */
13
+ function isChildProcessExec(content, lineContent) {
14
+ // Check for child_process import
15
+ const hasChildProcessImport = /require\s*\(\s*['"]child_process['"]\s*\)/.test(content) ||
16
+ /from\s+['"]child_process['"]/.test(content) ||
17
+ /import\s+.*child_process/.test(content) ||
18
+ /require\s*\(\s*['"]node:child_process['"]\s*\)/.test(content) ||
19
+ /from\s+['"]node:child_process['"]/.test(content);
20
+ // If no child_process import, this is likely RegExp.exec or similar
21
+ if (!hasChildProcessImport) {
22
+ return false;
23
+ }
24
+ // Check if this specific line is RegExp.exec pattern
25
+ // RegExp.exec is called as: regex.exec(string) or /pattern/.exec(string)
26
+ const isRegExpExec = /\.\s*exec\s*\(/.test(lineContent) && // Method call on an object
27
+ !/\bexec\s*\(/.test(lineContent.replace(/\.\s*exec\s*\(/, '')); // Not a standalone exec()
28
+ // Also check for common RegExp patterns
29
+ const isRegExpPattern = /\/[^/]+\/[gimsuy]*\.exec\s*\(/.test(lineContent) || // /pattern/.exec()
30
+ /new\s+RegExp\s*\([^)]+\)\.exec\s*\(/.test(lineContent) || // new RegExp().exec()
31
+ /regex\.exec\s*\(/i.test(lineContent) || // regex.exec()
32
+ /pattern\.exec\s*\(/i.test(lineContent) || // pattern.exec()
33
+ /match\.exec\s*\(/i.test(lineContent) || // match.exec()
34
+ /re\.exec\s*\(/i.test(lineContent); // re.exec()
35
+ if (isRegExpExec || isRegExpPattern) {
36
+ return false;
37
+ }
38
+ // Check if exec is imported/destructured from child_process
39
+ const execImported = /\{\s*[^}]*\bexec\b[^}]*\}\s*=\s*require\s*\(\s*['"]child_process['"]/.test(content) ||
40
+ /\{\s*[^}]*\bexec\b[^}]*\}\s*=\s*require\s*\(\s*['"]node:child_process['"]/.test(content) ||
41
+ /import\s+\{\s*[^}]*\bexec\b[^}]*\}\s+from\s+['"]child_process['"]/.test(content) ||
42
+ /import\s+\{\s*[^}]*\bexec\b[^}]*\}\s+from\s+['"]node:child_process['"]/.test(content);
43
+ // If exec is directly imported from child_process, standalone exec() is dangerous
44
+ if (execImported && /\bexec\s*\(/.test(lineContent)) {
45
+ return true;
46
+ }
47
+ // Check for child_process.exec() pattern
48
+ if (/child_process\.exec\s*\(/.test(lineContent) ||
49
+ /cp\.exec\s*\(/.test(lineContent) ||
50
+ /childProcess\.exec\s*\(/.test(lineContent)) {
51
+ return true;
52
+ }
53
+ // If we have child_process import but can't determine usage, be conservative
54
+ // Only flag if it looks like a standalone exec() call
55
+ return /\bexec\s*\(/.test(lineContent) && !/\.\s*exec\s*\(/.test(lineContent);
56
+ }
57
+ /**
58
+ * Check if schema validation is applied near a JSON.parse call
59
+ * Looks for zod, yup, joi, or similar validation patterns
60
+ */
61
+ function hasSchemaValidationNearby(content, lineNumber) {
62
+ const lines = content.split('\n');
63
+ const start = Math.max(0, lineNumber - 5);
64
+ const end = Math.min(lines.length, lineNumber + 10);
65
+ const context = lines.slice(start, end).join('\n');
66
+ const schemaValidationPatterns = [
67
+ // Zod patterns
68
+ /z\.(object|string|number|array|boolean)\s*\(/i,
69
+ /\.parse\s*\(/i,
70
+ /\.safeParse\s*\(/i,
71
+ /schema\.parse/i,
72
+ /Schema\.parse/i,
73
+ // Yup patterns
74
+ /yup\.(object|string|number|array|boolean)\s*\(/i,
75
+ /\.validate\s*\(/i,
76
+ /\.validateSync\s*\(/i,
77
+ // Joi patterns
78
+ /Joi\.(object|string|number|array|boolean)\s*\(/i,
79
+ /\.validateAsync\s*\(/i,
80
+ // Valibot patterns
81
+ /v\.(object|string|number|array|boolean)\s*\(/i,
82
+ // AJV patterns
83
+ /ajv\.compile/i,
84
+ /validate\s*\(\s*schema/i,
85
+ // TypeBox patterns
86
+ /Type\.(Object|String|Number|Array|Boolean)\s*\(/i,
87
+ // Generic validation patterns
88
+ /validateSchema/i,
89
+ /schemaValidator/i,
90
+ /parseAndValidate/i,
91
+ ];
92
+ return schemaValidationPatterns.some(p => p.test(context));
93
+ }
94
+ /**
95
+ * Check if path traversal protection is in place
96
+ * Looks for common sanitization patterns that prevent directory traversal attacks
97
+ */
98
+ function hasPathTraversalProtection(context, lineContent) {
99
+ const protectionPatterns = [
100
+ // Path normalization with base directory check
101
+ /path\.resolve\s*\([^)]+\).*\.startsWith\s*\(/i,
102
+ /\.startsWith\s*\([^)]*(?:baseDir|basePath|rootDir|uploadDir|allowedDir)/i,
103
+ // Explicit ".." rejection
104
+ /\.includes\s*\(\s*['"`]\.\.['"`]\s*\)/i,
105
+ /\.indexOf\s*\(\s*['"`]\.\.['"`]\s*\)/i,
106
+ /['"`]\.\.['"`].*(?:throw|reject|return|error)/i,
107
+ // Path sanitization libraries
108
+ /sanitizePath|sanitizeFilename|sanitize-filename/i,
109
+ /path-sanitizer|secure-path/i,
110
+ // Explicit path validation
111
+ /validatePath|isValidPath|checkPath|verifyPath/i,
112
+ /isPathAllowed|isAllowedPath|pathIsAllowed/i,
113
+ // Normalize and check pattern
114
+ /path\.normalize\s*\([^)]+\).*(?:startsWith|includes|indexOf)/i,
115
+ // Regex validation for safe characters only
116
+ /\/\^?\[a-zA-Z0-9_\-\.\\\/\]\+\$?\//, // Only alphanumeric, dash, underscore, dot
117
+ // Allowlist/whitelist patterns
118
+ /allowedExtensions|allowedTypes|whitelist/i,
119
+ /\.endsWith\s*\(\s*['"`]\.\w+['"`]\s*\)/i, // Extension check
120
+ // Path.basename to strip directory
121
+ /path\.basename\s*\(/i,
122
+ // Zod/validation for filename patterns
123
+ /z\.string\s*\(\s*\)\.regex\s*\(/i,
124
+ ];
125
+ return protectionPatterns.some(p => p.test(context) || p.test(lineContent));
126
+ }
127
+ /**
128
+ * Check if spawn/execFile/execSync is from child_process
129
+ */
130
+ function isChildProcessSpawn(content, lineContent) {
131
+ // Check for child_process import
132
+ const hasChildProcessImport = /require\s*\(\s*['"]child_process['"]\s*\)/.test(content) ||
133
+ /from\s+['"]child_process['"]/.test(content) ||
134
+ /require\s*\(\s*['"]node:child_process['"]\s*\)/.test(content) ||
135
+ /from\s+['"]node:child_process['"]/.test(content);
136
+ if (!hasChildProcessImport) {
137
+ return false;
138
+ }
139
+ // These functions are always from child_process when that module is imported
140
+ return /\b(spawn|spawnSync|execSync|execFile|execFileSync)\s*\(/.test(lineContent);
141
+ }
142
+ /**
143
+ * Check if a line is inside a try-catch block
144
+ * Looks for enclosing try { ... } catch pattern
145
+ */
146
+ function isInsideTryCatch(content, lineNumber) {
147
+ const lines = content.split('\n');
148
+ // Track brace depth and whether we're in a try block
149
+ let tryDepth = 0;
150
+ let inTryBlock = false;
151
+ let braceStack = [];
152
+ // Scan from start to the target line
153
+ for (let i = 0; i < lineNumber && i < lines.length; i++) {
154
+ const line = lines[i];
155
+ // Check for try keyword (not in a comment)
156
+ if (/\btry\s*\{/.test(line) && !(0, context_helpers_1.isComment)(line)) {
157
+ inTryBlock = true;
158
+ tryDepth++;
159
+ // Count opening braces on this line
160
+ const openBraces = (line.match(/\{/g) || []).length;
161
+ const closeBraces = (line.match(/\}/g) || []).length;
162
+ for (let j = 0; j < openBraces - closeBraces; j++) {
163
+ braceStack.push('try');
164
+ }
165
+ }
166
+ else if (/\bcatch\s*\(/.test(line) && !(0, context_helpers_1.isComment)(line)) {
167
+ // Entering catch block - still protected
168
+ // Don't decrement tryDepth yet
169
+ }
170
+ else if (/\bfinally\s*\{/.test(line) && !(0, context_helpers_1.isComment)(line)) {
171
+ // Entering finally block - still protected
172
+ }
173
+ else {
174
+ // Track regular braces
175
+ const openBraces = (line.match(/\{/g) || []).length;
176
+ const closeBraces = (line.match(/\}/g) || []).length;
177
+ for (let j = 0; j < openBraces; j++) {
178
+ braceStack.push(inTryBlock && tryDepth > 0 ? 'try' : 'other');
179
+ }
180
+ for (let j = 0; j < closeBraces; j++) {
181
+ const popped = braceStack.pop();
182
+ if (popped === 'try') {
183
+ tryDepth--;
184
+ if (tryDepth === 0) {
185
+ inTryBlock = false;
186
+ }
187
+ }
188
+ }
189
+ }
190
+ }
191
+ return tryDepth > 0;
192
+ }
193
+ /**
194
+ * Simpler heuristic: check if there's a try-catch in the same function scope
195
+ * Looks for try { before the line and } catch after, within reasonable bounds
196
+ */
197
+ function hasTryCatchNearby(content, lineNumber, windowSize = 20) {
198
+ const lines = content.split('\n');
199
+ const startLine = Math.max(0, lineNumber - windowSize);
200
+ const endLine = Math.min(lines.length, lineNumber + windowSize);
201
+ // Look backward for 'try {'
202
+ let foundTry = false;
203
+ for (let i = lineNumber - 1; i >= startLine; i--) {
204
+ const line = lines[i];
205
+ if (/\btry\s*\{/.test(line) && !(0, context_helpers_1.isComment)(line)) {
206
+ foundTry = true;
207
+ break;
208
+ }
209
+ // Stop if we hit a function boundary
210
+ if (/\b(function|async function|=>|class)\b/.test(line) && /\{/.test(line)) {
211
+ break;
212
+ }
213
+ }
214
+ if (!foundTry)
215
+ return false;
216
+ // Look forward for '} catch'
217
+ for (let i = lineNumber; i < endLine; i++) {
218
+ const line = lines[i];
219
+ if (/\}\s*catch\s*\(/.test(line) && !(0, context_helpers_1.isComment)(line)) {
220
+ return true;
221
+ }
222
+ // Stop if we hit another function boundary
223
+ if (i > lineNumber && /\b(function|async function|class)\b/.test(line) && /\{/.test(line)) {
224
+ break;
225
+ }
226
+ }
227
+ return false;
228
+ }
229
+ /**
230
+ * Check if file path indicates a low-risk context for JSON.parse
231
+ */
232
+ function isLowRiskJSONParseFile(filePath) {
233
+ // Test/mock files - skip or info only
234
+ if ((0, context_helpers_1.isTestOrMockFile)(filePath)) {
235
+ return 'test_fixture';
236
+ }
237
+ // Settings/preferences components - internal UI state
238
+ if (/\/(components|pages)\/(settings|preferences|config)/i.test(filePath)) {
239
+ return 'ui_state';
240
+ }
241
+ // Provider/context files - typically storing state in localStorage
242
+ if (/Provider\.(ts|tsx|js|jsx)$/i.test(filePath)) {
243
+ return 'ui_state';
244
+ }
245
+ // Modal/Dialog components - typically internal state
246
+ if (/(Modal|Dialog|Settings|Preferences)\.(ts|tsx|js|jsx)$/i.test(filePath)) {
247
+ return 'ui_state';
248
+ }
249
+ // __mocks__ directory
250
+ if (/__mocks__/i.test(filePath)) {
251
+ return 'test_fixture';
252
+ }
253
+ // fixtures directory
254
+ if (/\/(fixtures?|stubs?|mocks?)\//i.test(filePath)) {
255
+ return 'test_fixture';
256
+ }
257
+ // scripts/tools directories (internal tooling)
258
+ if (/\/(scripts?|tools?|cli)\//i.test(filePath)) {
259
+ return 'internal';
260
+ }
261
+ // Migration files
262
+ if (/migration/i.test(filePath)) {
263
+ return 'migration';
264
+ }
265
+ // Config files
266
+ if (/\/(config|settings|constants)\.(ts|js)/i.test(filePath)) {
267
+ return 'config';
268
+ }
269
+ return null;
270
+ }
271
+ /**
272
+ * Check if JSON.parse is parsing a trusted SDK response
273
+ * These are well-defined responses from known APIs and are safe to parse
274
+ */
275
+ function isTrustedSDKResponse(lineContent, content) {
276
+ const trustedPatterns = [
277
+ // OpenAI SDK responses
278
+ /JSON\.parse\s*\(\s*(?:response|completion|result|message)\.(?:content|text|data)/i,
279
+ /JSON\.parse\s*\(\s*(?:openai|anthropic|client)\./i,
280
+ // Fetch response.json() result (already parsed by fetch)
281
+ /JSON\.parse\s*\(\s*await\s+.*\.json\s*\(\s*\)\s*\)/i,
282
+ // SDK method results
283
+ /JSON\.parse\s*\(\s*(?:result|response)\.(?:choices|content|data|body)\[/i,
284
+ // AI SDK streaming results
285
+ /JSON\.parse\s*\(\s*(?:chunk|delta|part)\.(?:content|text)/i,
286
+ ];
287
+ if (trustedPatterns.some(p => p.test(lineContent))) {
288
+ return true;
289
+ }
290
+ // Check surrounding context for SDK usage
291
+ const sdkContextPatterns = [
292
+ /openai\..*\.create/i,
293
+ /anthropic\..*\.create/i,
294
+ /\.chat\.completions/i,
295
+ /\.messages\.create/i,
296
+ ];
297
+ return sdkContextPatterns.some(p => p.test(content));
298
+ }
299
+ function classifyJSONParseSource(lineContent, filePath) {
300
+ // First check file path for low-risk contexts
301
+ const fileBasedSource = isLowRiskJSONParseFile(filePath);
302
+ if (fileBasedSource) {
303
+ return fileBasedSource;
304
+ }
305
+ // User input - potentially dangerous
306
+ const userInputPatterns = [
307
+ /JSON\.parse\s*\(\s*(req|request)\.(body|query|params)/i,
308
+ /JSON\.parse\s*\(\s*event\.(body|queryStringParameters)/i, // AWS Lambda
309
+ /JSON\.parse\s*\(\s*ctx\.(request|body|query)/i, // Koa
310
+ /JSON\.parse\s*\(\s*(input|userInput|rawInput|payload)/i,
311
+ /JSON\.parse\s*\(\s*body\b/i, // Generic 'body' often means request body
312
+ ];
313
+ if (userInputPatterns.some(p => p.test(lineContent))) {
314
+ return 'user_input';
315
+ }
316
+ // localStorage/sessionStorage - client-side storage
317
+ const storagePatterns = [
318
+ /JSON\.parse\s*\(\s*localStorage\.getItem/i,
319
+ /JSON\.parse\s*\(\s*sessionStorage\.getItem/i,
320
+ /JSON\.parse\s*\(\s*window\.localStorage/i,
321
+ /JSON\.parse\s*\(\s*storage\.get/i,
322
+ /JSON\.parse\s*\(\s*saved\b/i, // Common pattern: const saved = localStorage.getItem(...); JSON.parse(saved)
323
+ /JSON\.parse\s*\(\s*stored\b/i,
324
+ ];
325
+ if (storagePatterns.some(p => p.test(lineContent))) {
326
+ return 'local_storage';
327
+ }
328
+ // Database results - internal data
329
+ const databasePatterns = [
330
+ /JSON\.parse\s*\(\s*(row|result|record|doc|document)\./i,
331
+ /JSON\.parse\s*\(\s*\w+\.(data|json|metadata|embedding)\)/i,
332
+ /JSON\.parse\s*\(\s*\w+\[['"]?\w+['"]?\]\.(data|json|embedding)/i,
333
+ /JSON\.parse\s*\(\s*item\.\w+\)/i, // ORM iteration: items.map(item => JSON.parse(item.field))
334
+ /JSON\.parse\s*\(\s*\w+\.content\)/i, // Parsing content field from DB
335
+ ];
336
+ if (databasePatterns.some(p => p.test(lineContent))) {
337
+ return 'database';
338
+ }
339
+ // Editor state, internal caches, UI state
340
+ const internalPatterns = [
341
+ /JSON\.parse\s*\(\s*(state|cache|stored|saved|cached)/i,
342
+ /JSON\.parse\s*\(\s*this\.(state|cache|data)/i,
343
+ /JSON\.parse\s*\(\s*\w+State\)/i,
344
+ /JSON\.parse\s*\(\s*editorState/i,
345
+ /JSON\.parse\s*\(\s*parsed\b/i, // JSON.parse(parsed) - likely already validated
346
+ /JSON\.parse\s*\(\s*settings\b/i, // Settings data
347
+ /JSON\.parse\s*\(\s*preferences\b/i,
348
+ ];
349
+ if (internalPatterns.some(p => p.test(lineContent))) {
350
+ return 'internal';
351
+ }
352
+ // Node content in editor apps (e.g., noda-os nodes have JSON content)
353
+ if (/JSON\.parse\s*\(\s*(node|note|document|entry)\.(content|body|data)\)/i.test(lineContent)) {
354
+ return 'database';
355
+ }
356
+ return 'unknown';
357
+ }
358
+ const DANGEROUS_FUNCTIONS = [
359
+ // Code execution
360
+ {
361
+ name: 'eval() usage',
362
+ pattern: /\beval\s*\(/gi,
363
+ severity: 'critical',
364
+ description: 'eval() executes arbitrary code and is a major security risk',
365
+ suggestedFix: 'Use JSON.parse() for JSON data, or refactor to avoid dynamic code execution',
366
+ },
367
+ {
368
+ name: 'Function constructor',
369
+ pattern: /new\s+Function\s*\(/gi,
370
+ severity: 'critical',
371
+ description: 'Function constructor can execute arbitrary code like eval()',
372
+ suggestedFix: 'Refactor to use static functions or safe alternatives',
373
+ },
374
+ {
375
+ name: 'setTimeout/setInterval with string',
376
+ pattern: /set(Timeout|Interval)\s*\(\s*['"`]/gi,
377
+ severity: 'high',
378
+ description: 'setTimeout/setInterval with string argument acts like eval()',
379
+ suggestedFix: 'Pass a function reference instead of a string',
380
+ },
381
+ // Command injection
382
+ {
383
+ name: 'child_process exec',
384
+ pattern: /\b(exec|execSync|spawn|spawnSync|execFile)\s*\(/gi,
385
+ severity: 'high',
386
+ description: 'Shell command execution can lead to command injection',
387
+ suggestedFix: 'Validate and sanitize all inputs, prefer execFile over exec',
388
+ },
389
+ {
390
+ name: 'os.system/subprocess (Python)',
391
+ pattern: /\b(os\.system|subprocess\.(call|run|Popen|check_output))\s*\(/gi,
392
+ severity: 'high',
393
+ description: 'Shell command execution can lead to command injection',
394
+ suggestedFix: 'Use subprocess with shell=False and pass arguments as a list',
395
+ languages: ['py'],
396
+ },
397
+ // SQL injection risks
398
+ {
399
+ name: 'Raw SQL query construction',
400
+ pattern: /\.(query|execute|raw)\s*\(\s*[`'"].*\$\{|\.query\s*\(\s*['"].*\+/gi,
401
+ severity: 'critical',
402
+ description: 'String concatenation in SQL queries can lead to SQL injection',
403
+ suggestedFix: 'Use parameterized queries or prepared statements',
404
+ },
405
+ {
406
+ name: 'SQL template literal',
407
+ pattern: /`SELECT.*FROM.*WHERE.*\$\{|`INSERT.*INTO.*VALUES.*\$\{|`UPDATE.*SET.*\$\{|`DELETE.*FROM.*WHERE.*\$\{/gi,
408
+ severity: 'critical',
409
+ description: 'Template literals in SQL queries can lead to SQL injection',
410
+ suggestedFix: 'Use parameterized queries with placeholders (?, $1, etc.)',
411
+ },
412
+ // XSS risks
413
+ {
414
+ name: 'innerHTML assignment',
415
+ pattern: /\.innerHTML\s*=|\.outerHTML\s*=/gi,
416
+ severity: 'high',
417
+ description: 'Direct innerHTML assignment can lead to XSS vulnerabilities',
418
+ suggestedFix: 'Use textContent for text, or sanitize HTML with DOMPurify',
419
+ },
420
+ {
421
+ name: 'document.write',
422
+ pattern: /document\.write\s*\(/gi,
423
+ severity: 'high',
424
+ description: 'document.write can introduce XSS vulnerabilities',
425
+ suggestedFix: 'Use DOM manipulation methods instead',
426
+ },
427
+ {
428
+ name: 'dangerouslySetInnerHTML',
429
+ pattern: /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html\s*:/gi,
430
+ severity: 'high',
431
+ description: 'dangerouslySetInnerHTML can lead to XSS if content is not sanitized',
432
+ suggestedFix: 'Sanitize HTML content with DOMPurify before rendering',
433
+ },
434
+ // Deserialization
435
+ {
436
+ name: 'Unsafe deserialization',
437
+ pattern: /\b(pickle\.loads?|yaml\.load\s*\((?!.*Loader)|unserialize|Marshal\.load)\s*\(/gi,
438
+ severity: 'critical',
439
+ description: 'Unsafe deserialization can lead to remote code execution',
440
+ suggestedFix: 'Use safe loaders (yaml.safe_load) or validate input before deserializing',
441
+ },
442
+ // Note: JSON.parse is handled specially with source-aware severity - see below
443
+ // Note: request.json() is NOT a dangerous function - see schema validation rules
444
+ // File system risks
445
+ {
446
+ name: 'Dynamic file path',
447
+ pattern: /\b(readFile|writeFile|readFileSync|writeFileSync|createReadStream|createWriteStream)\s*\(\s*[^'"]/gi,
448
+ severity: 'medium',
449
+ description: 'Dynamic file paths can lead to path traversal attacks',
450
+ suggestedFix: 'Validate and sanitize file paths, use path.resolve with a base directory',
451
+ },
452
+ {
453
+ name: 'Path traversal risk',
454
+ pattern: /path\.(join|resolve)\s*\([^)]*req\.(params|query|body)/gi,
455
+ severity: 'high',
456
+ description: 'User input in file paths can lead to path traversal attacks',
457
+ suggestedFix: 'Validate paths and ensure they stay within allowed directories',
458
+ },
459
+ // Crypto weaknesses
460
+ {
461
+ name: 'Math.random for security',
462
+ pattern: /Math\.random\s*\(\s*\)/gi,
463
+ severity: 'medium',
464
+ description: 'Math.random() is not cryptographically secure',
465
+ suggestedFix: 'Use crypto.randomBytes() or crypto.getRandomValues() for security-sensitive operations',
466
+ },
467
+ // Regex DoS
468
+ {
469
+ name: 'Potentially unsafe regex',
470
+ pattern: /new\s+RegExp\s*\(\s*[^'"]/gi,
471
+ severity: 'medium',
472
+ description: 'Dynamic regex construction can lead to ReDoS attacks',
473
+ suggestedFix: 'Validate regex patterns and consider using safe-regex library',
474
+ },
475
+ // Prototype pollution
476
+ {
477
+ name: 'Object.assign with user input',
478
+ pattern: /Object\.assign\s*\(\s*\{\s*\}\s*,\s*(req\.|request\.|body|params|query)/gi,
479
+ severity: 'high',
480
+ description: 'Object.assign with user input can lead to prototype pollution',
481
+ suggestedFix: 'Validate and sanitize input, or use a safe merge function',
482
+ },
483
+ {
484
+ name: 'Spread operator with user input',
485
+ pattern: /\{\s*\.\.\.req\.(body|params|query)|\.\.\.request\.(body|params|query)/gi,
486
+ severity: 'medium',
487
+ description: 'Spreading user input can lead to mass assignment vulnerabilities',
488
+ suggestedFix: 'Explicitly pick allowed properties instead of spreading all input',
489
+ },
490
+ ];
491
+ // Check if file matches language filter
492
+ function matchesLanguage(filePath, languages) {
493
+ if (!languages || languages.length === 0)
494
+ return true;
495
+ const ext = filePath.split('.').pop()?.toLowerCase() || '';
496
+ return languages.some(lang => {
497
+ if (lang === 'py')
498
+ return ext === 'py';
499
+ if (lang === 'js')
500
+ return ['js', 'jsx', 'mjs', 'cjs'].includes(ext);
501
+ if (lang === 'ts')
502
+ return ['ts', 'tsx'].includes(ext);
503
+ return ext === lang;
504
+ });
505
+ }
506
+ // Check if innerHTML/dangerouslySetInnerHTML uses static content only
507
+ function isStaticHTMLContent(lineContent, content, lineNumber) {
508
+ const lines = content.split('\n');
509
+ // Get surrounding context (5 lines before and after)
510
+ const contextStart = Math.max(0, lineNumber - 6);
511
+ const contextEnd = Math.min(lines.length, lineNumber + 5);
512
+ const context = lines.slice(contextStart, contextEnd).join('\n');
513
+ // Static HTML indicators - string literals only
514
+ const staticIndicators = [
515
+ /innerHTML\s*=\s*['"`][^'"`]*['"`]/, // innerHTML = "static string"
516
+ /innerHTML\s*=\s*`[^$]*`/, // innerHTML = `static template without ${}`
517
+ /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*['"`]/, // React static string
518
+ ];
519
+ // Dynamic content indicators (red flags)
520
+ const dynamicIndicators = [
521
+ /\$\{[^}]+\}/, // Template interpolation ${...}
522
+ /innerHTML\s*=.*\+/, // String concatenation with +
523
+ /innerHTML\s*\+=\s*/, // Append operation
524
+ /\breq\.|\.params|\.query|\.body/, // User input (req.params, req.query, req.body)
525
+ /\bprops\./, // Component props
526
+ /\bstate\./, // Component state
527
+ /\.value\b/, // Input value
528
+ /dangerouslySetInnerHTML\s*=\s*\{\s*\{\s*__html:\s*[^'"`]/, // React dynamic
529
+ ];
530
+ const isStatic = staticIndicators.some(p => p.test(lineContent));
531
+ const isDynamic = dynamicIndicators.some(p => p.test(context));
532
+ return isStatic && !isDynamic;
533
+ }
534
+ /**
535
+ * Check if eval/exec/Function has only static literal inputs (no user data)
536
+ * Static inputs like eval('({ mode: "production" })') are low risk
537
+ */
538
+ function hasOnlyStaticInputs(lineContent, content, lineNumber) {
539
+ const lines = content.split('\n');
540
+ // Check if the argument to eval/exec/Function is a string literal only
541
+ const staticPatterns = [
542
+ /eval\s*\(\s*['"`][^'"`$]*['"`]\s*\)/, // eval('static string')
543
+ /eval\s*\(\s*`[^$`]*`\s*\)/, // eval(`static template without ${}`)
544
+ /new\s+Function\s*\(\s*['"`][^'"`$]*['"`]\s*\)/, // new Function('static')
545
+ /execSync\s*\(\s*['"`][^'"`$]*['"`]\s*\)/, // execSync('static command')
546
+ /exec\s*\(\s*['"`][^'"`$]*['"`]/, // exec('static command'
547
+ ];
548
+ if (staticPatterns.some(p => p.test(lineContent))) {
549
+ return true;
550
+ }
551
+ // Check surrounding context for user input flowing in
552
+ const userInputIndicators = [
553
+ /\$\{/, // Template interpolation
554
+ /\+\s*\w+/, // String concatenation with variable
555
+ /req\.|request\.|body\.|params\.|query\./i, // Request data
556
+ /user[Ii]nput|userCode|userCommand/, // User input variables
557
+ /args\[|argv\[/, // Command line args
558
+ ];
559
+ const contextStart = Math.max(0, lineNumber - 3);
560
+ const contextEnd = Math.min(lines.length, lineNumber + 1);
561
+ const context = lines.slice(contextStart, contextEnd).join('\n');
562
+ // If no user input indicators found, likely static
563
+ return !userInputIndicators.some(p => p.test(context));
564
+ }
565
+ /**
566
+ * Check if SQL query uses whitelist validation pattern
567
+ * e.g., columns validated against allowedColumns array before use
568
+ */
569
+ function hasSQLWhitelistValidation(content, lineNumber) {
570
+ const lines = content.split('\n');
571
+ const contextStart = Math.max(0, lineNumber - 15);
572
+ const contextEnd = Math.min(lines.length, lineNumber + 5);
573
+ const context = lines.slice(contextStart, contextEnd).join('\n');
574
+ // Whitelist/allowlist validation patterns
575
+ const whitelistPatterns = [
576
+ /allowed\w*\s*=\s*\[/i, // allowedColumns = [...]
577
+ /whitelist\w*\s*=\s*\[/i, // whitelistFields = [...]
578
+ /valid\w*\s*=\s*\[/i, // validColumns = [...]
579
+ /\.filter\s*\([^)]*\.includes\s*\(/i, // .filter(c => allowed.includes(c))
580
+ /\.includes\s*\([^)]*\)/i, // allowedColumns.includes(col)
581
+ /\.every\s*\([^)]*\.includes/i, // columns.every(c => allowed.includes(c))
582
+ /if\s*\(\s*!.*\.includes/i, // if (!allowed.includes(...))
583
+ ];
584
+ return whitelistPatterns.some(p => p.test(context));
585
+ }
586
+ /**
587
+ * Check if dangerouslySetInnerHTML is used with DOMPurify sanitization
588
+ */
589
+ function hasDOMPurifySanitization(lineContent, content, lineNumber) {
590
+ const lines = content.split('\n');
591
+ const contextStart = Math.max(0, lineNumber - 10);
592
+ const contextEnd = Math.min(lines.length, lineNumber + 5);
593
+ const context = lines.slice(contextStart, contextEnd).join('\n');
594
+ // DOMPurify sanitization patterns
595
+ const sanitizationPatterns = [
596
+ /DOMPurify\.sanitize/i,
597
+ /sanitize\s*\(/i,
598
+ /purify\s*\(/i,
599
+ /xss\s*\(/i,
600
+ /clean\s*\(/i,
601
+ /sanitizeHtml/i,
602
+ /escapeHtml/i,
603
+ /sanitized/i,
604
+ /purified/i,
605
+ ];
606
+ return sanitizationPatterns.some(p => p.test(context));
607
+ }
608
+ /**
609
+ * Check if data flows to an LLM prompt rather than a DOM sink
610
+ * LLM prompts are NOT XSS - they're prompt injection (different risk profile)
611
+ */
612
+ function isLLMPromptContext(lineContent, content, filePath) {
613
+ // File path indicators of AI/LLM code
614
+ const aiFilePatterns = [
615
+ /\/(ai|llm|chat|openai|anthropic|gpt|claude)\//i,
616
+ /\/(assistants?|agents?|prompts?)\//i,
617
+ /(chat|ai|llm|prompt|assistant).*\.(ts|js|tsx|jsx)$/i,
618
+ ];
619
+ if (aiFilePatterns.some(p => p.test(filePath))) {
620
+ return true;
621
+ }
622
+ // Content patterns suggesting LLM API usage
623
+ const llmApiPatterns = [
624
+ /\.create\s*\(\s*\{[^}]*messages\s*:/i, // OpenAI/Anthropic SDK
625
+ /openai|anthropic|claude|gpt-4|gpt-3/i, // AI service mentions
626
+ /\bprompt\s*[=:+]/i, // prompt assignment
627
+ /\bsystemPrompt|userPrompt|assistantPrompt/i, // Prompt variables
628
+ /completion|chat\.create|messages\.create/i, // API calls
629
+ /\bmessages\s*:\s*\[/i, // Messages array
630
+ /role:\s*['"`](user|assistant|system)['"`]/i, // Message roles
631
+ ];
632
+ // Check the line and surrounding context
633
+ const lines = content.split('\n');
634
+ const lineIndex = lines.findIndex(l => l === lineContent || l.includes(lineContent.trim()));
635
+ const startLine = Math.max(0, lineIndex - 10);
636
+ const endLine = Math.min(lines.length, lineIndex + 10);
637
+ const context = lines.slice(startLine, endLine).join('\n');
638
+ return llmApiPatterns.some(p => p.test(lineContent) || p.test(context));
639
+ }
640
+ /**
641
+ * Check if this is a static bootstrap script (e.g., localStorage theme reader)
642
+ * These are very low risk even with dangerouslySetInnerHTML
643
+ */
644
+ function isStaticBootstrapScript(_lineContent, content, lineNumber) {
645
+ const lines = content.split('\n');
646
+ const contextStart = Math.max(0, lineNumber - 10);
647
+ const contextEnd = Math.min(lines.length, lineNumber + 5);
648
+ const context = lines.slice(contextStart, contextEnd).join('\n');
649
+ // Bootstrap script indicators (reading from localStorage, setting attributes)
650
+ const bootstrapPatterns = [
651
+ /localStorage\.getItem/i,
652
+ /document\.documentElement\.setAttribute/i,
653
+ /data-(theme|font|mode)/i,
654
+ /classList\.(add|remove|toggle)/i,
655
+ /\.dataset\./i,
656
+ ];
657
+ // Dangerous patterns that disqualify as safe bootstrap
658
+ const dangerousPatterns = [
659
+ /\$\{.*\}/, // Template interpolation
660
+ /\+\s*[a-zA-Z]/, // String concatenation with variable
661
+ /innerHTML\s*=\s*[a-zA-Z]/, // innerHTML set to variable directly
662
+ /fetch\s*\(/, // Network requests
663
+ /\.(query|params|body)/, // User input
664
+ /location\.(search|hash)/, // URL parameters
665
+ /document\.cookie/, // Cookie access
666
+ ];
667
+ const hasBootstrapPatterns = bootstrapPatterns.some(p => p.test(context));
668
+ const hasDangerousPatterns = dangerousPatterns.some(p => p.test(context));
669
+ return hasBootstrapPatterns && !hasDangerousPatterns;
670
+ }
671
+ /**
672
+ * Check if Math.random() is used for cosmetic/UI purposes (not security)
673
+ * Cosmetic uses: CSS values, animations, UI variations, demo data
674
+ * Security uses: tokens, IDs, cryptographic operations, session management
675
+ */
676
+ function isCosmeticMathRandom(lineContent, content, lineNumber) {
677
+ const lines = content.split('\n');
678
+ // Check the line itself for cosmetic indicators
679
+ const cosmeticLinePatterns = [
680
+ // CSS/style values
681
+ /['"`]\s*\$\{.*Math\.random.*\}\s*%['"`]/, // `${Math.random() * 40 + 50}%`
682
+ /Math\.random.*\s*\+\s*['"`]%['"`]/, // Math.random() * 40 + '%'
683
+ /Math\.random.*\)\s*\*\s*\d+\s*\+\s*\d+\s*\}\s*%/, // }) * 40 + 50}%
684
+ /return\s+`.*Math\.random.*%`/, // return `${...}%`
685
+ /width:\s*['"`].*Math\.random/i, // width: `${Math.random()...}%`
686
+ /height:\s*['"`].*Math\.random/i, // height: `${Math.random()...}%`
687
+ /opacity:\s*['"`]?.*Math\.random/i, // opacity: Math.random()
688
+ /transform:\s*['"`]?.*Math\.random/i, // transform: translate(...)
689
+ /rotate\(.*Math\.random/i, // rotate(Math.random() * 360)
690
+ /translate\(.*Math\.random/i, // translate(Math.random() * 100)
691
+ /scale\(.*Math\.random/i, // scale(Math.random() * 2)
692
+ // Color/animation values
693
+ /rgba?\(.*Math\.random/i, // rgb(Math.random() * 255, ...)
694
+ /hsl\(.*Math\.random/i, // hsl(Math.random() * 360, ...)
695
+ /Math\.random.*\*\s*360/, // Math.random() * 360 (degrees/hue)
696
+ /Math\.random.*\*\s*255/, // Math.random() * 255 (RGB values)
697
+ // Array/list randomization for UI
698
+ /Math\.floor\(Math\.random.*\.length\)/, // Math.floor(Math.random() * array.length)
699
+ /\[Math\.floor\(Math\.random/, // array[Math.floor(Math.random()...)]
700
+ // Demo/placeholder data
701
+ /Math\.random.*\*\s*\d+\s*\+\s*\d+.*\bpx\b/i, // Math.random() * 100 + 50 + 'px'
702
+ /Math\.random.*\*\s*\d+\s*\+\s*\d+.*\bms\b/i, // Math.random() * 1000 + 500 + 'ms'
703
+ /Math\.random.*\*\s*\d+\s*\+\s*\d+.*\bs\b/i, // Math.random() * 5 + 2 + 's'
704
+ // UI identifier generation (short strings for element IDs, keys, etc.)
705
+ /Math\.random\(\)\.toString\(36\)\.substring\(/, // .toString(36).substring(2, 9) - short UI IDs
706
+ /Math\.random\(\)\.toString\(36\)\.substr\(/, // .substr() variant
707
+ /Math\.random\(\)\.toString\(36\)\.slice\(/, // .slice() variant
708
+ /Math\.random\(\)\.toString\(16\)\.substring\(/, // .toString(16).substring() - hex UI IDs
709
+ /Math\.random\(\)\.toString\(16\)\.slice\(/, // hex slice variant
710
+ ];
711
+ if (cosmeticLinePatterns.some(p => p.test(lineContent))) {
712
+ return true;
713
+ }
714
+ // Check surrounding context (5 lines before and after)
715
+ const contextStart = Math.max(0, lineNumber - 5);
716
+ const contextEnd = Math.min(lines.length, lineNumber + 5);
717
+ const context = lines.slice(contextStart, contextEnd).join('\n');
718
+ // Context indicators of cosmetic use
719
+ const cosmeticContextPatterns = [
720
+ // UI component files
721
+ /\/(components?|ui|widgets?|animations?|contexts?)\//i,
722
+ // Style-related variables/functions
723
+ /\b(style|styles|css|className|animation|transition)/i,
724
+ /\b(width|height|opacity|color|transform|rotate|scale|translate)/i,
725
+ // Demo/example data
726
+ /\b(demo|example|placeholder|mock|fake|sample|test)Data/i,
727
+ /\b(random|shuffle|pick|choose).*\b(color|item|element|option)/i,
728
+ // Animation/timing
729
+ /setTimeout.*Math\.random/i,
730
+ /setInterval.*Math\.random/i,
731
+ /delay.*Math\.random/i,
732
+ /duration.*Math\.random/i,
733
+ // UI state variations
734
+ /\b(variant|theme|layout|position).*Math\.random/i,
735
+ // UI identifier variable names (toast, notification, element, component IDs)
736
+ /\b(toast|notification|element|component|widget|modal|dialog|popup).*id\b/i,
737
+ /\bid\s*=.*Math\.random/i,
738
+ /\bkey\s*=.*Math\.random/i, // React keys
739
+ /\btempId|temporaryId|uniqueId\b/i,
740
+ ];
741
+ if (cosmeticContextPatterns.some(p => p.test(context))) {
742
+ return true;
743
+ }
744
+ // Security-sensitive patterns that override cosmetic detection
745
+ const securityPatterns = [
746
+ /\b(token|secret|key|password|credential|signature)/i,
747
+ /\b(auth|crypto|encrypt|decrypt|hash)/i,
748
+ /\b(session|nonce|salt)\b/i,
749
+ /Math\.random.*\*\s*1e\d+/, // Math.random() * 1e16 (large numbers for IDs)
750
+ ];
751
+ if (securityPatterns.some(p => p.test(lineContent) || p.test(context))) {
752
+ return false; // Not cosmetic - this is security-sensitive
753
+ }
754
+ // Check for .toString(36) WITHOUT substring/slice/substr (security token pattern)
755
+ // If it has substring/slice/substr, it's already caught by cosmeticLinePatterns above
756
+ const hasToString36WithoutTruncation = /Math\.random\(\)\.toString\(36\)/.test(lineContent) &&
757
+ !/\.(substring|substr|slice)\(/.test(lineContent);
758
+ const hasToString16WithoutTruncation = /Math\.random\(\)\.toString\(16\)/.test(lineContent) &&
759
+ !/\.(substring|substr|slice)\(/.test(lineContent);
760
+ if (hasToString36WithoutTruncation || hasToString16WithoutTruncation) {
761
+ return false; // Full-length toString() without truncation - likely security token
762
+ }
763
+ return false; // Default to flagging if unclear
764
+ }
765
+ function detectDangerousFunctions(content, filePath) {
766
+ const vulnerabilities = [];
767
+ // Skip scanner/fixture files to avoid self-detection
768
+ if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath)) {
769
+ return vulnerabilities;
770
+ }
771
+ const lines = content.split('\n');
772
+ const isTestFile = (0, context_helpers_1.isTestOrMockFile)(filePath);
773
+ lines.forEach((line, index) => {
774
+ // Skip comment lines
775
+ if ((0, context_helpers_1.isComment)(line))
776
+ return;
777
+ for (const funcPattern of DANGEROUS_FUNCTIONS) {
778
+ // Check language filter
779
+ if (!matchesLanguage(filePath, funcPattern.languages))
780
+ continue;
781
+ const regex = new RegExp(funcPattern.pattern.source, funcPattern.pattern.flags);
782
+ if (regex.test(line)) {
783
+ // Special handling for innerHTML patterns
784
+ if (funcPattern.name === 'innerHTML assignment' ||
785
+ funcPattern.name === 'dangerouslySetInnerHTML') {
786
+ // Check if this uses static content only
787
+ if (isStaticHTMLContent(line, content, index)) {
788
+ vulnerabilities.push({
789
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
790
+ filePath,
791
+ lineNumber: index + 1,
792
+ lineContent: line.trim(),
793
+ severity: 'info',
794
+ category: 'dangerous_function',
795
+ title: funcPattern.name + ' (static content)',
796
+ description: 'Static HTML assignment detected. Generally safe for hardcoded content, but consider using textContent for plain text or proper DOM methods for dynamic content.',
797
+ suggestedFix: 'If this is plain text, use textContent instead. If HTML must be used, ensure it is static and does not come from user input.',
798
+ confidence: 'low',
799
+ layer: 2,
800
+ });
801
+ break; // Only report once per line
802
+ }
803
+ // Check if DOMPurify or similar sanitization is used
804
+ if (hasDOMPurifySanitization(line, content, index)) {
805
+ vulnerabilities.push({
806
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
807
+ filePath,
808
+ lineNumber: index + 1,
809
+ lineContent: line.trim(),
810
+ severity: 'info',
811
+ category: 'dangerous_function',
812
+ title: funcPattern.name + ' (sanitized)',
813
+ description: 'HTML is sanitized before rendering (DOMPurify or similar detected). This is the recommended pattern for rendering user-generated HTML.',
814
+ suggestedFix: 'Ensure DOMPurify is configured correctly and kept up to date.',
815
+ confidence: 'low',
816
+ layer: 2,
817
+ });
818
+ break; // Only report once per line
819
+ }
820
+ // Check if this is a static bootstrap script (e.g., theme/font loader)
821
+ if (isStaticBootstrapScript(line, content, index)) {
822
+ vulnerabilities.push({
823
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
824
+ filePath,
825
+ lineNumber: index + 1,
826
+ lineContent: line.trim(),
827
+ severity: 'info',
828
+ category: 'dangerous_function',
829
+ title: funcPattern.name + ' (static bootstrap script)',
830
+ description: 'This appears to be a static bootstrap script (e.g., reading localStorage for theme/font preferences). Low risk as no untrusted data is interpolated into the HTML/JS.',
831
+ suggestedFix: 'Verify no user-controlled data is interpolated into the script content.',
832
+ confidence: 'low',
833
+ layer: 2,
834
+ });
835
+ break; // Only report once per line
836
+ }
837
+ // Check if this is in LLM prompt context (not XSS - it's prompt injection)
838
+ if (isLLMPromptContext(line, content, filePath)) {
839
+ vulnerabilities.push({
840
+ id: `dangerous-func-${filePath}-${index + 1}-prompt-injection`,
841
+ filePath,
842
+ lineNumber: index + 1,
843
+ lineContent: line.trim(),
844
+ severity: 'info',
845
+ category: 'ai_pattern',
846
+ title: 'Potential prompt injection risk',
847
+ description: 'User content is being used in an LLM prompt context. This is NOT XSS (the content goes to an AI, not a DOM). However, untrusted content in prompts may lead to prompt injection attacks.',
848
+ suggestedFix: 'Consider input validation, content filtering, or structured prompts to limit prompt injection risk.',
849
+ confidence: 'low',
850
+ layer: 2,
851
+ });
852
+ break; // Only report once per line
853
+ }
854
+ // Dynamic content - full severity, needs AI validation
855
+ let severity = funcPattern.severity;
856
+ if (isTestFile) {
857
+ severity = 'low';
858
+ }
859
+ vulnerabilities.push({
860
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
861
+ filePath,
862
+ lineNumber: index + 1,
863
+ lineContent: line.trim(),
864
+ severity,
865
+ category: 'dangerous_function',
866
+ title: funcPattern.name,
867
+ description: funcPattern.description + ' This appears to use dynamic content which increases XSS risk.' + (isTestFile ? ' (in test file)' : ''),
868
+ suggestedFix: funcPattern.suggestedFix,
869
+ confidence: isTestFile ? 'low' : 'high',
870
+ layer: 2,
871
+ requiresAIValidation: true, // Dynamic HTML needs validation
872
+ });
873
+ break; // Only report once per line
874
+ }
875
+ // Note: JSON.parse is now handled by standalone detectJSONParseSafe() function
876
+ // which provides better source-aware severity classification
877
+ // Special handling for eval and Function constructor
878
+ if (funcPattern.name === 'eval() usage' || funcPattern.name === 'Function constructor') {
879
+ // Suppress entirely in test files - test files legitimately test eval behavior
880
+ if (isTestFile) {
881
+ break; // Skip reporting entirely
882
+ }
883
+ // Check if eval is inside a test assertion (expect(), test(), it(), describe())
884
+ const testAssertionPattern = /\b(expect|test|it|describe)\s*\(/;
885
+ if (testAssertionPattern.test(line)) {
886
+ break; // Skip reporting - this is testing eval behavior
887
+ }
888
+ // Check if inputs are static literals (low risk)
889
+ if (hasOnlyStaticInputs(line, content, index)) {
890
+ vulnerabilities.push({
891
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
892
+ filePath,
893
+ lineNumber: index + 1,
894
+ lineContent: line.trim(),
895
+ severity: 'info',
896
+ category: 'dangerous_function',
897
+ title: funcPattern.name + ' (static input)',
898
+ description: 'eval/Function with static string literal input. Lower risk than dynamic input, but consider refactoring to avoid eval entirely.',
899
+ suggestedFix: 'Consider using JSON.parse() for JSON data or refactoring to avoid eval.',
900
+ confidence: 'low',
901
+ layer: 2,
902
+ });
903
+ break;
904
+ }
905
+ vulnerabilities.push({
906
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
907
+ filePath,
908
+ lineNumber: index + 1,
909
+ lineContent: line.trim(),
910
+ severity: funcPattern.severity,
911
+ category: 'dangerous_function',
912
+ title: funcPattern.name,
913
+ description: funcPattern.description,
914
+ suggestedFix: funcPattern.suggestedFix,
915
+ confidence: 'high',
916
+ layer: 2,
917
+ requiresAIValidation: true, // Code execution patterns need validation
918
+ });
919
+ break;
920
+ }
921
+ // Special handling for child_process exec - verify it's not RegExp.exec
922
+ if (funcPattern.name === 'child_process exec') {
923
+ // First check if this is actually from child_process (not RegExp.exec)
924
+ const isExecMatch = /\bexec\s*\(/.test(line);
925
+ const isOtherMatch = /\b(execSync|spawn|spawnSync|execFile)\s*\(/.test(line);
926
+ if (isExecMatch && !isOtherMatch) {
927
+ // This matched 'exec(' - verify it's from child_process
928
+ if (!isChildProcessExec(content, line)) {
929
+ // This is RegExp.exec or similar - skip
930
+ break;
931
+ }
932
+ }
933
+ else if (isOtherMatch) {
934
+ // This matched spawn/execSync/etc - verify child_process import
935
+ if (!isChildProcessSpawn(content, line)) {
936
+ // No child_process import - skip
937
+ break;
938
+ }
939
+ }
940
+ if (hasOnlyStaticInputs(line, content, index)) {
941
+ vulnerabilities.push({
942
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
943
+ filePath,
944
+ lineNumber: index + 1,
945
+ lineContent: line.trim(),
946
+ severity: 'info',
947
+ category: 'dangerous_function',
948
+ title: funcPattern.name + ' (static command)',
949
+ description: 'exec/execSync with hardcoded command. Lower risk than dynamic commands, but ensure command does not change based on user input.',
950
+ suggestedFix: 'If command is truly static, this is generally acceptable. For dynamic commands, validate and sanitize inputs.',
951
+ confidence: 'low',
952
+ layer: 2,
953
+ });
954
+ break;
955
+ }
956
+ }
957
+ // Special handling for SQL patterns - check for whitelist validation
958
+ if (funcPattern.name === 'Raw SQL query construction' || funcPattern.name === 'SQL template literal') {
959
+ if (hasSQLWhitelistValidation(content, index)) {
960
+ vulnerabilities.push({
961
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
962
+ filePath,
963
+ lineNumber: index + 1,
964
+ lineContent: line.trim(),
965
+ severity: 'info',
966
+ category: 'dangerous_function',
967
+ title: funcPattern.name + ' (whitelist validated)',
968
+ description: 'SQL query with dynamic content, but whitelist/allowlist validation detected. This is a safer pattern that limits injection risk.',
969
+ suggestedFix: 'Ensure the whitelist is comprehensive and cannot be bypassed. Consider using parameterized queries for additional safety.',
970
+ confidence: 'low',
971
+ layer: 2,
972
+ });
973
+ break;
974
+ }
975
+ }
976
+ // Special handling for Dynamic file path - skip for utility files
977
+ // Utility functions designed to work with file paths are expected to take path parameters
978
+ if (funcPattern.name === 'Dynamic file path') {
979
+ // Skip for utility/lib/helper files - these are internal functions, not API handlers
980
+ const isUtilityFile = /\/(utils?|lib|helpers?|services?|modules?)\//i.test(filePath);
981
+ // Skip if function name suggests it's designed for file operations
982
+ const isFileOperationFunction = /\b(checksum|hash|digest|fingerprint|read|write|load|save|get|set|copy|move|delete)File/i.test(content.slice(Math.max(0, index - 200), index + 100));
983
+ // Skip CLI command files - these take paths from command-line args (controlled inputs)
984
+ const isCLIFile = /\/(cli|commands?|bin)\//i.test(filePath) ||
985
+ /\/src\/(index|main|cli)\.(ts|js)$/i.test(filePath);
986
+ // Skip GitHub Action files - these process repo files (controlled environment)
987
+ const isGitHubAction = /github-action/i.test(filePath) ||
988
+ /action\.(ts|js|yml|yaml)$/i.test(filePath);
989
+ // Check for schema validation patterns in the surrounding context
990
+ // Zod, Yup, Joi, or regex validation on the input
991
+ const contextWindow = content.slice(Math.max(0, content.indexOf(line) - 500), content.indexOf(line) + line.length);
992
+ const hasSchemaValidation = /z\.(string|object)\s*\(\s*\)\.regex\s*\(/i.test(contextWindow) ||
993
+ /z\.enum\s*\(/i.test(contextWindow) ||
994
+ /\.regex\s*\(\s*\/.*\/\s*\)/i.test(contextWindow) || // .regex(/.../)
995
+ /\.match\s*\(\s*\/.*\/\s*\)/i.test(contextWindow) || // .match(/.../)
996
+ /\.(schema|validate)\s*\(/i.test(contextWindow) ||
997
+ /joi\./i.test(contextWindow) ||
998
+ /yup\./i.test(contextWindow);
999
+ // Check for path sanitization patterns
1000
+ const hasPathSanitization = hasPathTraversalProtection(contextWindow, line);
1001
+ if (isUtilityFile || isFileOperationFunction || isTestFile || isCLIFile || isGitHubAction || hasSchemaValidation || hasPathSanitization) {
1002
+ // Skip entirely for utility functions or when schema validation is present
1003
+ break;
1004
+ }
1005
+ }
1006
+ // Special handling for Path traversal risk - check for sanitization
1007
+ if (funcPattern.name === 'Path traversal risk') {
1008
+ const contextWindow = content.slice(Math.max(0, content.indexOf(line) - 500), content.indexOf(line) + line.length + 200);
1009
+ // Check for path sanitization patterns
1010
+ if (hasPathTraversalProtection(contextWindow, line)) {
1011
+ vulnerabilities.push({
1012
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1013
+ filePath,
1014
+ lineNumber: index + 1,
1015
+ lineContent: line.trim(),
1016
+ severity: 'info',
1017
+ category: 'dangerous_function',
1018
+ title: funcPattern.name + ' (sanitized)',
1019
+ description: 'User input in file path, but path traversal protection detected. Verify sanitization is comprehensive.',
1020
+ suggestedFix: 'Ensure path.resolve() result is checked against base directory and ".." sequences are rejected.',
1021
+ confidence: 'low',
1022
+ layer: 2,
1023
+ });
1024
+ break;
1025
+ }
1026
+ }
1027
+ // Special handling for Math.random() - skip cosmetic/UI uses
1028
+ if (funcPattern.name === 'Math.random for security') {
1029
+ // Check if this is cosmetic use (CSS, animations, UI variations)
1030
+ if (isCosmeticMathRandom(line, content, index)) {
1031
+ // Skip entirely - this is not a security concern
1032
+ break;
1033
+ }
1034
+ }
1035
+ // Standard handling for all other patterns
1036
+ let severity = funcPattern.severity;
1037
+ let confidence = 'high';
1038
+ if (isTestFile) {
1039
+ if (severity === 'critical') {
1040
+ severity = 'medium';
1041
+ }
1042
+ else if (severity === 'high') {
1043
+ severity = 'low';
1044
+ }
1045
+ else {
1046
+ severity = 'info';
1047
+ }
1048
+ confidence = 'low';
1049
+ }
1050
+ vulnerabilities.push({
1051
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1052
+ filePath,
1053
+ lineNumber: index + 1,
1054
+ lineContent: line.trim(),
1055
+ severity,
1056
+ category: 'dangerous_function',
1057
+ title: funcPattern.name,
1058
+ description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
1059
+ suggestedFix: funcPattern.suggestedFix,
1060
+ confidence,
1061
+ layer: 2,
1062
+ });
1063
+ break; // Only report once per line
1064
+ }
1065
+ }
1066
+ });
1067
+ // Additional standalone checks (not in DANGEROUS_FUNCTIONS array)
1068
+ // JSON.parse source-aware detection
1069
+ detectJSONParseSafe(content, filePath, isTestFile, vulnerabilities);
1070
+ // request.json() / req.json() schema validation suggestion
1071
+ detectRequestJsonValidation(content, filePath, isTestFile, vulnerabilities);
1072
+ return vulnerabilities;
1073
+ }
1074
+ /**
1075
+ * Detect JSON.parse usage with source-aware severity
1076
+ * Much smarter than simple pattern matching - considers try/catch and data source
1077
+ */
1078
+ function detectJSONParseSafe(content, filePath, isTestFile, vulnerabilities) {
1079
+ const lines = content.split('\n');
1080
+ const jsonParsePattern = /JSON\.parse\s*\(/gi;
1081
+ // Track instances per file to aggregate noisy patterns
1082
+ const instances = [];
1083
+ lines.forEach((line, index) => {
1084
+ if ((0, context_helpers_1.isComment)(line))
1085
+ return;
1086
+ jsonParsePattern.lastIndex = 0;
1087
+ if (!jsonParsePattern.test(line))
1088
+ return;
1089
+ const jsonSource = classifyJSONParseSource(line, filePath);
1090
+ // Skip migration files entirely - they're internal tooling
1091
+ if (jsonSource === 'migration')
1092
+ return;
1093
+ // Skip test fixtures entirely - they're intentionally parsing test data
1094
+ if (jsonSource === 'test_fixture')
1095
+ return;
1096
+ // Skip trusted SDK responses - these are well-defined and safe to parse
1097
+ if (isTrustedSDKResponse(line, content))
1098
+ return;
1099
+ // Check if JSON.parse is inside a try-catch block
1100
+ const insideTryCatch = isInsideTryCatch(content, index) || hasTryCatchNearby(content, index);
1101
+ // Check if schema validation is applied after JSON.parse
1102
+ const hasSchemaValidation = hasSchemaValidationNearby(content, index);
1103
+ // If inside try-catch with safe source, suppress entirely - this is perfectly fine
1104
+ if (insideTryCatch && ['local_storage', 'database', 'config', 'internal', 'ui_state'].includes(jsonSource)) {
1105
+ return;
1106
+ }
1107
+ // If schema validation is present, this is properly handled
1108
+ if (hasSchemaValidation) {
1109
+ return;
1110
+ }
1111
+ // UI state (settings, providers, modals) - very low risk, aggregate or skip
1112
+ if (jsonSource === 'ui_state') {
1113
+ // Only track for aggregation, don't report individually
1114
+ instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1115
+ return;
1116
+ }
1117
+ // Determine severity based on source and error handling
1118
+ let severity;
1119
+ let description;
1120
+ let suggestedFix;
1121
+ let confidence = 'medium';
1122
+ if (insideTryCatch) {
1123
+ // Already has error handling
1124
+ switch (jsonSource) {
1125
+ case 'user_input':
1126
+ severity = 'low';
1127
+ description = 'JSON.parse on user input is wrapped in try-catch. Consider adding schema validation (zod/yup) to validate the parsed structure.';
1128
+ suggestedFix = 'Add schema validation after parsing: const validated = schema.parse(JSON.parse(input))';
1129
+ confidence = 'low';
1130
+ break;
1131
+ default:
1132
+ // With try-catch and non-user source, this is fine - don't report
1133
+ return;
1134
+ }
1135
+ }
1136
+ else {
1137
+ // No try-catch
1138
+ switch (jsonSource) {
1139
+ case 'user_input':
1140
+ severity = 'medium';
1141
+ description = 'JSON.parse on user input without schema validation. Malformed input will crash; malicious input may have unexpected shape.';
1142
+ suggestedFix = 'Use a schema validation library (zod, yup, joi): try { const data = schema.parse(JSON.parse(body)) } catch (e) { return 400 }';
1143
+ confidence = 'high';
1144
+ break;
1145
+ case 'local_storage':
1146
+ severity = 'info';
1147
+ description = 'JSON.parse on localStorage data. Consider adding try-catch for robustness against corrupted data.';
1148
+ suggestedFix = 'Wrap in try-catch to handle corrupted localStorage gracefully.';
1149
+ confidence = 'low';
1150
+ break;
1151
+ case 'database':
1152
+ // Database content parsing is very common and low-risk
1153
+ instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1154
+ return; // Will be aggregated below
1155
+ case 'config':
1156
+ case 'internal':
1157
+ severity = 'info';
1158
+ description = `JSON.parse on ${jsonSource.replace('_', ' ')} data without error handling. Low risk but consider defensive coding.`;
1159
+ suggestedFix = 'Consider adding try-catch for robustness.';
1160
+ confidence = 'low';
1161
+ break;
1162
+ default:
1163
+ // Unknown source - track for potential aggregation
1164
+ instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1165
+ return; // Will be evaluated below based on aggregation
1166
+ }
1167
+ }
1168
+ // Downgrade test files
1169
+ if (isTestFile) {
1170
+ severity = 'info';
1171
+ confidence = 'low';
1172
+ description += ' (in test file)';
1173
+ }
1174
+ vulnerabilities.push({
1175
+ id: `json-parse-${filePath}-${index + 1}`,
1176
+ filePath,
1177
+ lineNumber: index + 1,
1178
+ lineContent: line.trim(),
1179
+ severity,
1180
+ category: 'dangerous_function',
1181
+ title: 'JSON.parse usage',
1182
+ description,
1183
+ suggestedFix,
1184
+ confidence,
1185
+ layer: 2,
1186
+ });
1187
+ });
1188
+ // Aggregate low-risk JSON.parse instances if there are many
1189
+ if (instances.length >= 3) {
1190
+ // Create single aggregated finding instead of N individual findings
1191
+ const lineNumbers = instances.map(i => i.lineNumber).slice(0, 5);
1192
+ const moreText = instances.length > 5 ? `... (${instances.length} total)` : '';
1193
+ vulnerabilities.push({
1194
+ id: `json-parse-aggregated-${filePath}`,
1195
+ filePath,
1196
+ lineNumber: instances[0].lineNumber,
1197
+ lineContent: `${instances.length} instances across this file`,
1198
+ severity: 'info',
1199
+ category: 'dangerous_function',
1200
+ title: `JSON.parse usage (${instances.length} instances)`,
1201
+ description: `JSON.parse detected. Consider adding error handling and schema validation if parsing user input.${isTestFile ? ' (in test file)' : ''}\n\nFound ${instances.length} occurrences at lines: ${lineNumbers.join(', ')}${moreText}`,
1202
+ suggestedFix: 'Add try-catch for error handling. If parsing user input, add schema validation.',
1203
+ confidence: 'low',
1204
+ layer: 2,
1205
+ });
1206
+ }
1207
+ else if (instances.length > 0 && instances.length < 3) {
1208
+ // Report individually for small counts
1209
+ for (const instance of instances) {
1210
+ vulnerabilities.push({
1211
+ id: `json-parse-${filePath}-${instance.lineNumber}`,
1212
+ filePath,
1213
+ lineNumber: instance.lineNumber,
1214
+ lineContent: instance.lineContent,
1215
+ severity: 'info',
1216
+ category: 'dangerous_function',
1217
+ title: 'JSON.parse usage',
1218
+ description: `JSON.parse on ${instance.source.replace('_', ' ')} data without error handling. Low risk but consider defensive coding.${isTestFile ? ' (in test file)' : ''}`,
1219
+ suggestedFix: 'Consider adding try-catch for robustness.',
1220
+ confidence: 'low',
1221
+ layer: 2,
1222
+ });
1223
+ }
1224
+ }
1225
+ }
1226
+ /**
1227
+ * Check if this file appears to have form/input validation elsewhere
1228
+ * (manual checks on body fields, type guards, etc.)
1229
+ */
1230
+ function hasManualValidation(content) {
1231
+ const manualValidationPatterns = [
1232
+ // Type checking / type guards
1233
+ /typeof\s+\w+\s*[!=]==?\s*['"](?:string|number|boolean|object)['"]|Array\.isArray\s*\(/i,
1234
+ // Field existence checks followed by throws/returns
1235
+ /if\s*\(\s*!(?:body|data|input)\.\w+\s*\)\s*\{?\s*(throw|return)/i,
1236
+ // Property access with type assertion comments or inline validation
1237
+ /\b(body|data|input)\s*as\s+\w+/i, // Type assertion
1238
+ // Manual validation with error handling
1239
+ /if\s*\(\s*![\w.]+\s*\|\|\s*typeof\s+[\w.]+/i,
1240
+ // Using type predicates
1241
+ /is\w+\s*\([\w.]+\)/i, // isFoo(bar) pattern
1242
+ ];
1243
+ return manualValidationPatterns.some(p => p.test(content));
1244
+ }
1245
+ /**
1246
+ * Check if route has throwing auth helper (getCurrentUserId, requireAuth, etc.)
1247
+ * Routes with throwing auth helpers are already protected
1248
+ */
1249
+ function hasThrowingAuthHelper(content) {
1250
+ const throwingAuthPatterns = [
1251
+ /\bgetCurrentUserId\s*\(/i,
1252
+ /\brequireAuth\s*\(/i,
1253
+ /\bensureAuth\s*\(/i,
1254
+ /\bauth\s*\(\s*\)\s*\.protect\s*\(/i, // Clerk: auth().protect()
1255
+ /\bcurrentUser\s*\(\s*\)/i, // Clerk: currentUser()
1256
+ /\bgetServerSession\s*\([^)]*\)/i, // NextAuth
1257
+ /\bauth\s*\(\s*\)/i, // Generic auth() call
1258
+ /\bcheckAuth\s*\(/i,
1259
+ /\bverifyAuth\s*\(/i,
1260
+ /\bvalidateAuth\s*\(/i,
1261
+ /\bassertAuth\s*\(/i,
1262
+ /\bgetAuth\s*\(/i,
1263
+ /\brequireUser\s*\(/i,
1264
+ /\bgetUser\s*\(\s*\)/i, // supabase.auth.getUser()
1265
+ /const\s+\{\s*user\s*\}\s*=\s*await/i, // Destructuring pattern
1266
+ ];
1267
+ return throwingAuthPatterns.some(p => p.test(content));
1268
+ }
1269
+ /**
1270
+ * Detect request.json() / req.json() and suggest schema validation
1271
+ * This is NOT a dangerous function - it's a prompt for best practices
1272
+ */
1273
+ function detectRequestJsonValidation(content, filePath, isTestFile, vulnerabilities) {
1274
+ // Only check API route files
1275
+ if (!/\/(api|routes?|handlers?|controllers?)\//i.test(filePath) &&
1276
+ !/route\.(ts|js)$/i.test(filePath)) {
1277
+ return;
1278
+ }
1279
+ // Skip if route has throwing auth helper - these are already protected routes
1280
+ // and the schema validation suggestion is lower priority
1281
+ if (hasThrowingAuthHelper(content)) {
1282
+ return;
1283
+ }
1284
+ const lines = content.split('\n');
1285
+ // Matches: request.json(), req.json(), await request.json(), etc.
1286
+ const requestJsonPattern = /\b(request|req)\.json\s*\(\s*\)/gi;
1287
+ // Check if file has schema validation (library-based)
1288
+ const hasSchemaLibrary = /\b(zod|yup|joi|ajv|superstruct|valibot|typebox)\b/i.test(content) ||
1289
+ /\.parse\s*\(|\.validate\s*\(|\.safeParse\s*\(/i.test(content);
1290
+ // If file has schema library validation, don't report
1291
+ if (hasSchemaLibrary)
1292
+ return;
1293
+ // Check for manual validation patterns (less robust but still indicates intent)
1294
+ const hasManualCheck = hasManualValidation(content);
1295
+ // Track instances for potential aggregation
1296
+ const instances = [];
1297
+ lines.forEach((line, index) => {
1298
+ if ((0, context_helpers_1.isComment)(line))
1299
+ return;
1300
+ requestJsonPattern.lastIndex = 0;
1301
+ if (!requestJsonPattern.test(line))
1302
+ return;
1303
+ // Check if there's validation nearby (within 10 lines after)
1304
+ const startCheck = index;
1305
+ const endCheck = Math.min(lines.length, index + 10);
1306
+ const nearbyContent = lines.slice(startCheck, endCheck).join('\n');
1307
+ // If there's validation in the nearby lines, skip
1308
+ if (/\.parse\s*\(|\.validate\s*\(|\.safeParse\s*\(|schema\./i.test(nearbyContent)) {
1309
+ return;
1310
+ }
1311
+ // If manual validation is present, skip individual reporting but track for aggregate
1312
+ if (hasManualCheck) {
1313
+ instances.push({ lineNumber: index + 1, lineContent: line.trim() });
1314
+ return;
1315
+ }
1316
+ if (isTestFile) {
1317
+ return; // Don't report in test files
1318
+ }
1319
+ instances.push({ lineNumber: index + 1, lineContent: line.trim() });
1320
+ });
1321
+ // Don't report if no instances found
1322
+ if (instances.length === 0)
1323
+ return;
1324
+ // If manual validation exists, create a single info-level note
1325
+ if (hasManualCheck && instances.length > 0) {
1326
+ vulnerabilities.push({
1327
+ id: `request-json-manual-${filePath}`,
1328
+ filePath,
1329
+ lineNumber: instances[0].lineNumber,
1330
+ lineContent: instances[0].lineContent,
1331
+ severity: 'info',
1332
+ category: 'dangerous_function',
1333
+ title: 'Request body with manual validation',
1334
+ description: `API endpoint parses request body with manual validation patterns detected. Consider using a schema library (zod, yup) for more robust type-safe validation.`,
1335
+ suggestedFix: 'While manual validation works, schema libraries provide better TypeScript integration and error messages.',
1336
+ confidence: 'low',
1337
+ layer: 2,
1338
+ });
1339
+ return;
1340
+ }
1341
+ // Aggregate if multiple instances without validation
1342
+ if (instances.length >= 2) {
1343
+ const lineNumbers = instances.map(i => i.lineNumber).slice(0, 5);
1344
+ vulnerabilities.push({
1345
+ id: `request-json-aggregated-${filePath}`,
1346
+ filePath,
1347
+ lineNumber: instances[0].lineNumber,
1348
+ lineContent: `${instances.length} instances`,
1349
+ severity: 'info',
1350
+ category: 'dangerous_function',
1351
+ title: `Request body without schema validation (${instances.length} instances)`,
1352
+ description: `API endpoint parses request body without visible schema validation at lines: ${lineNumbers.join(', ')}. Consider validating the shape of incoming data.`,
1353
+ suggestedFix: 'Add schema validation (e.g., zod): const body = await request.json(); const data = schema.parse(body);',
1354
+ confidence: 'low',
1355
+ layer: 2,
1356
+ });
1357
+ }
1358
+ else {
1359
+ // Single instance
1360
+ vulnerabilities.push({
1361
+ id: `request-json-${filePath}-${instances[0].lineNumber}`,
1362
+ filePath,
1363
+ lineNumber: instances[0].lineNumber,
1364
+ lineContent: instances[0].lineContent,
1365
+ severity: 'info',
1366
+ category: 'dangerous_function',
1367
+ title: 'Request body without schema validation',
1368
+ description: 'API endpoint parses request body without visible schema validation. Consider validating the shape of incoming data.',
1369
+ suggestedFix: 'Add schema validation (e.g., zod): const body = await request.json(); const data = schema.parse(body);',
1370
+ confidence: 'low',
1371
+ layer: 2,
1372
+ });
1373
+ }
1374
+ }
1375
+ //# sourceMappingURL=dangerous-functions.js.map