@oculum/scanner 1.0.9 → 1.0.11

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 (365) hide show
  1. package/dist/baseline/diff.d.ts +32 -0
  2. package/dist/baseline/diff.d.ts.map +1 -0
  3. package/dist/baseline/diff.js +119 -0
  4. package/dist/baseline/diff.js.map +1 -0
  5. package/dist/baseline/index.d.ts +9 -0
  6. package/dist/baseline/index.d.ts.map +1 -0
  7. package/dist/baseline/index.js +19 -0
  8. package/dist/baseline/index.js.map +1 -0
  9. package/dist/baseline/manager.d.ts +67 -0
  10. package/dist/baseline/manager.d.ts.map +1 -0
  11. package/dist/baseline/manager.js +180 -0
  12. package/dist/baseline/manager.js.map +1 -0
  13. package/dist/baseline/types.d.ts +91 -0
  14. package/dist/baseline/types.d.ts.map +1 -0
  15. package/dist/baseline/types.js +12 -0
  16. package/dist/baseline/types.js.map +1 -0
  17. package/dist/formatters/cli-terminal.d.ts +38 -0
  18. package/dist/formatters/cli-terminal.d.ts.map +1 -1
  19. package/dist/formatters/cli-terminal.js +365 -42
  20. package/dist/formatters/cli-terminal.js.map +1 -1
  21. package/dist/formatters/github-comment.d.ts +1 -1
  22. package/dist/formatters/github-comment.d.ts.map +1 -1
  23. package/dist/formatters/github-comment.js +75 -11
  24. package/dist/formatters/github-comment.js.map +1 -1
  25. package/dist/formatters/index.d.ts +1 -1
  26. package/dist/formatters/index.d.ts.map +1 -1
  27. package/dist/formatters/index.js +4 -1
  28. package/dist/formatters/index.js.map +1 -1
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -1
  31. package/dist/index.js +155 -16
  32. package/dist/index.js.map +1 -1
  33. package/dist/layer1/config-audit.d.ts.map +1 -1
  34. package/dist/layer1/config-audit.js +20 -3
  35. package/dist/layer1/config-audit.js.map +1 -1
  36. package/dist/layer1/config-mcp-audit.d.ts +20 -0
  37. package/dist/layer1/config-mcp-audit.d.ts.map +1 -0
  38. package/dist/layer1/config-mcp-audit.js +239 -0
  39. package/dist/layer1/config-mcp-audit.js.map +1 -0
  40. package/dist/layer1/index.d.ts +1 -0
  41. package/dist/layer1/index.d.ts.map +1 -1
  42. package/dist/layer1/index.js +9 -1
  43. package/dist/layer1/index.js.map +1 -1
  44. package/dist/layer2/ai-agent-tools.d.ts.map +1 -1
  45. package/dist/layer2/ai-agent-tools.js +303 -0
  46. package/dist/layer2/ai-agent-tools.js.map +1 -1
  47. package/dist/layer2/ai-endpoint-protection.d.ts.map +1 -1
  48. package/dist/layer2/ai-endpoint-protection.js +17 -3
  49. package/dist/layer2/ai-endpoint-protection.js.map +1 -1
  50. package/dist/layer2/ai-execution-sinks.d.ts.map +1 -1
  51. package/dist/layer2/ai-execution-sinks.js +462 -12
  52. package/dist/layer2/ai-execution-sinks.js.map +1 -1
  53. package/dist/layer2/ai-fingerprinting.d.ts.map +1 -1
  54. package/dist/layer2/ai-fingerprinting.js +3 -0
  55. package/dist/layer2/ai-fingerprinting.js.map +1 -1
  56. package/dist/layer2/ai-mcp-security.d.ts +17 -0
  57. package/dist/layer2/ai-mcp-security.d.ts.map +1 -0
  58. package/dist/layer2/ai-mcp-security.js +679 -0
  59. package/dist/layer2/ai-mcp-security.js.map +1 -0
  60. package/dist/layer2/ai-package-hallucination.d.ts +19 -0
  61. package/dist/layer2/ai-package-hallucination.d.ts.map +1 -0
  62. package/dist/layer2/ai-package-hallucination.js +696 -0
  63. package/dist/layer2/ai-package-hallucination.js.map +1 -0
  64. package/dist/layer2/ai-prompt-hygiene.d.ts.map +1 -1
  65. package/dist/layer2/ai-prompt-hygiene.js +495 -9
  66. package/dist/layer2/ai-prompt-hygiene.js.map +1 -1
  67. package/dist/layer2/ai-rag-safety.d.ts.map +1 -1
  68. package/dist/layer2/ai-rag-safety.js +372 -1
  69. package/dist/layer2/ai-rag-safety.js.map +1 -1
  70. package/dist/layer2/auth-antipatterns.d.ts.map +1 -1
  71. package/dist/layer2/auth-antipatterns.js +4 -0
  72. package/dist/layer2/auth-antipatterns.js.map +1 -1
  73. package/dist/layer2/byok-patterns.d.ts.map +1 -1
  74. package/dist/layer2/byok-patterns.js +3 -0
  75. package/dist/layer2/byok-patterns.js.map +1 -1
  76. package/dist/layer2/dangerous-functions/child-process.d.ts +16 -0
  77. package/dist/layer2/dangerous-functions/child-process.d.ts.map +1 -0
  78. package/dist/layer2/dangerous-functions/child-process.js +74 -0
  79. package/dist/layer2/dangerous-functions/child-process.js.map +1 -0
  80. package/dist/layer2/dangerous-functions/dom-xss.d.ts +29 -0
  81. package/dist/layer2/dangerous-functions/dom-xss.d.ts.map +1 -0
  82. package/dist/layer2/dangerous-functions/dom-xss.js +179 -0
  83. package/dist/layer2/dangerous-functions/dom-xss.js.map +1 -0
  84. package/dist/layer2/dangerous-functions/index.d.ts +13 -0
  85. package/dist/layer2/dangerous-functions/index.d.ts.map +1 -0
  86. package/dist/layer2/dangerous-functions/index.js +621 -0
  87. package/dist/layer2/dangerous-functions/index.js.map +1 -0
  88. package/dist/layer2/dangerous-functions/json-parse.d.ts +31 -0
  89. package/dist/layer2/dangerous-functions/json-parse.d.ts.map +1 -0
  90. package/dist/layer2/dangerous-functions/json-parse.js +319 -0
  91. package/dist/layer2/dangerous-functions/json-parse.js.map +1 -0
  92. package/dist/layer2/dangerous-functions/math-random.d.ts +61 -0
  93. package/dist/layer2/dangerous-functions/math-random.d.ts.map +1 -0
  94. package/dist/layer2/dangerous-functions/math-random.js +459 -0
  95. package/dist/layer2/dangerous-functions/math-random.js.map +1 -0
  96. package/dist/layer2/dangerous-functions/patterns.d.ts +21 -0
  97. package/dist/layer2/dangerous-functions/patterns.d.ts.map +1 -0
  98. package/dist/layer2/dangerous-functions/patterns.js +161 -0
  99. package/dist/layer2/dangerous-functions/patterns.js.map +1 -0
  100. package/dist/layer2/dangerous-functions/request-validation.d.ts +13 -0
  101. package/dist/layer2/dangerous-functions/request-validation.d.ts.map +1 -0
  102. package/dist/layer2/dangerous-functions/request-validation.js +119 -0
  103. package/dist/layer2/dangerous-functions/request-validation.js.map +1 -0
  104. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts +23 -0
  105. package/dist/layer2/dangerous-functions/utils/control-flow.d.ts.map +1 -0
  106. package/dist/layer2/dangerous-functions/utils/control-flow.js +149 -0
  107. package/dist/layer2/dangerous-functions/utils/control-flow.js.map +1 -0
  108. package/dist/layer2/dangerous-functions/utils/helpers.d.ts +31 -0
  109. package/dist/layer2/dangerous-functions/utils/helpers.d.ts.map +1 -0
  110. package/dist/layer2/dangerous-functions/utils/helpers.js +124 -0
  111. package/dist/layer2/dangerous-functions/utils/helpers.js.map +1 -0
  112. package/dist/layer2/dangerous-functions/utils/index.d.ts +9 -0
  113. package/dist/layer2/dangerous-functions/utils/index.d.ts.map +1 -0
  114. package/dist/layer2/dangerous-functions/utils/index.js +23 -0
  115. package/dist/layer2/dangerous-functions/utils/index.js.map +1 -0
  116. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts +22 -0
  117. package/dist/layer2/dangerous-functions/utils/schema-validation.d.ts.map +1 -0
  118. package/dist/layer2/dangerous-functions/utils/schema-validation.js +89 -0
  119. package/dist/layer2/dangerous-functions/utils/schema-validation.js.map +1 -0
  120. package/dist/layer2/data-exposure.d.ts.map +1 -1
  121. package/dist/layer2/data-exposure.js +3 -0
  122. package/dist/layer2/data-exposure.js.map +1 -1
  123. package/dist/layer2/framework-checks.d.ts.map +1 -1
  124. package/dist/layer2/framework-checks.js +3 -0
  125. package/dist/layer2/framework-checks.js.map +1 -1
  126. package/dist/layer2/index.d.ts +3 -0
  127. package/dist/layer2/index.d.ts.map +1 -1
  128. package/dist/layer2/index.js +61 -2
  129. package/dist/layer2/index.js.map +1 -1
  130. package/dist/layer2/logic-gates.d.ts.map +1 -1
  131. package/dist/layer2/logic-gates.js +4 -0
  132. package/dist/layer2/logic-gates.js.map +1 -1
  133. package/dist/layer2/model-supply-chain.d.ts +20 -0
  134. package/dist/layer2/model-supply-chain.d.ts.map +1 -0
  135. package/dist/layer2/model-supply-chain.js +376 -0
  136. package/dist/layer2/model-supply-chain.js.map +1 -0
  137. package/dist/layer2/risky-imports.d.ts.map +1 -1
  138. package/dist/layer2/risky-imports.js +4 -0
  139. package/dist/layer2/risky-imports.js.map +1 -1
  140. package/dist/layer2/variables.d.ts.map +1 -1
  141. package/dist/layer2/variables.js +4 -0
  142. package/dist/layer2/variables.js.map +1 -1
  143. package/dist/layer3/anthropic/auto-dismiss.d.ts +24 -0
  144. package/dist/layer3/anthropic/auto-dismiss.d.ts.map +1 -0
  145. package/dist/layer3/anthropic/auto-dismiss.js +188 -0
  146. package/dist/layer3/anthropic/auto-dismiss.js.map +1 -0
  147. package/dist/layer3/anthropic/clients.d.ts +44 -0
  148. package/dist/layer3/anthropic/clients.d.ts.map +1 -0
  149. package/dist/layer3/anthropic/clients.js +81 -0
  150. package/dist/layer3/anthropic/clients.js.map +1 -0
  151. package/dist/layer3/anthropic/index.d.ts +41 -0
  152. package/dist/layer3/anthropic/index.d.ts.map +1 -0
  153. package/dist/layer3/anthropic/index.js +141 -0
  154. package/dist/layer3/anthropic/index.js.map +1 -0
  155. package/dist/layer3/anthropic/prompts/index.d.ts +8 -0
  156. package/dist/layer3/anthropic/prompts/index.d.ts.map +1 -0
  157. package/dist/layer3/anthropic/prompts/index.js +14 -0
  158. package/dist/layer3/anthropic/prompts/index.js.map +1 -0
  159. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts +15 -0
  160. package/dist/layer3/anthropic/prompts/semantic-analysis.d.ts.map +1 -0
  161. package/dist/layer3/anthropic/prompts/semantic-analysis.js +169 -0
  162. package/dist/layer3/anthropic/prompts/semantic-analysis.js.map +1 -0
  163. package/dist/layer3/anthropic/prompts/validation.d.ts +12 -0
  164. package/dist/layer3/anthropic/prompts/validation.d.ts.map +1 -0
  165. package/dist/layer3/anthropic/prompts/validation.js +421 -0
  166. package/dist/layer3/anthropic/prompts/validation.js.map +1 -0
  167. package/dist/layer3/anthropic/providers/anthropic.d.ts +21 -0
  168. package/dist/layer3/anthropic/providers/anthropic.d.ts.map +1 -0
  169. package/dist/layer3/anthropic/providers/anthropic.js +266 -0
  170. package/dist/layer3/anthropic/providers/anthropic.js.map +1 -0
  171. package/dist/layer3/anthropic/providers/index.d.ts +8 -0
  172. package/dist/layer3/anthropic/providers/index.d.ts.map +1 -0
  173. package/dist/layer3/anthropic/providers/index.js +15 -0
  174. package/dist/layer3/anthropic/providers/index.js.map +1 -0
  175. package/dist/layer3/anthropic/providers/openai.d.ts +18 -0
  176. package/dist/layer3/anthropic/providers/openai.d.ts.map +1 -0
  177. package/dist/layer3/anthropic/providers/openai.js +340 -0
  178. package/dist/layer3/anthropic/providers/openai.js.map +1 -0
  179. package/dist/layer3/anthropic/request-builder.d.ts +20 -0
  180. package/dist/layer3/anthropic/request-builder.d.ts.map +1 -0
  181. package/dist/layer3/anthropic/request-builder.js +134 -0
  182. package/dist/layer3/anthropic/request-builder.js.map +1 -0
  183. package/dist/layer3/anthropic/types.d.ts +88 -0
  184. package/dist/layer3/anthropic/types.d.ts.map +1 -0
  185. package/dist/layer3/anthropic/types.js +38 -0
  186. package/dist/layer3/anthropic/types.js.map +1 -0
  187. package/dist/layer3/anthropic/utils/index.d.ts +9 -0
  188. package/dist/layer3/anthropic/utils/index.d.ts.map +1 -0
  189. package/dist/layer3/anthropic/utils/index.js +24 -0
  190. package/dist/layer3/anthropic/utils/index.js.map +1 -0
  191. package/dist/layer3/anthropic/utils/path-helpers.d.ts +21 -0
  192. package/dist/layer3/anthropic/utils/path-helpers.d.ts.map +1 -0
  193. package/dist/layer3/anthropic/utils/path-helpers.js +69 -0
  194. package/dist/layer3/anthropic/utils/path-helpers.js.map +1 -0
  195. package/dist/layer3/anthropic/utils/response-parser.d.ts +40 -0
  196. package/dist/layer3/anthropic/utils/response-parser.d.ts.map +1 -0
  197. package/dist/layer3/anthropic/utils/response-parser.js +285 -0
  198. package/dist/layer3/anthropic/utils/response-parser.js.map +1 -0
  199. package/dist/layer3/anthropic/utils/retry.d.ts +15 -0
  200. package/dist/layer3/anthropic/utils/retry.d.ts.map +1 -0
  201. package/dist/layer3/anthropic/utils/retry.js +62 -0
  202. package/dist/layer3/anthropic/utils/retry.js.map +1 -0
  203. package/dist/layer3/index.d.ts +1 -0
  204. package/dist/layer3/index.d.ts.map +1 -1
  205. package/dist/layer3/index.js +16 -6
  206. package/dist/layer3/index.js.map +1 -1
  207. package/dist/layer3/osv-check.d.ts +75 -0
  208. package/dist/layer3/osv-check.d.ts.map +1 -0
  209. package/dist/layer3/osv-check.js +308 -0
  210. package/dist/layer3/osv-check.js.map +1 -0
  211. package/dist/rules/framework-fixes.d.ts +48 -0
  212. package/dist/rules/framework-fixes.d.ts.map +1 -0
  213. package/dist/rules/framework-fixes.js +439 -0
  214. package/dist/rules/framework-fixes.js.map +1 -0
  215. package/dist/rules/index.d.ts +8 -0
  216. package/dist/rules/index.d.ts.map +1 -0
  217. package/dist/rules/index.js +18 -0
  218. package/dist/rules/index.js.map +1 -0
  219. package/dist/rules/metadata.d.ts +43 -0
  220. package/dist/rules/metadata.d.ts.map +1 -0
  221. package/dist/rules/metadata.js +734 -0
  222. package/dist/rules/metadata.js.map +1 -0
  223. package/dist/suppression/config-loader.d.ts +74 -0
  224. package/dist/suppression/config-loader.d.ts.map +1 -0
  225. package/dist/suppression/config-loader.js +424 -0
  226. package/dist/suppression/config-loader.js.map +1 -0
  227. package/dist/suppression/hash.d.ts +48 -0
  228. package/dist/suppression/hash.d.ts.map +1 -0
  229. package/dist/suppression/hash.js +88 -0
  230. package/dist/suppression/hash.js.map +1 -0
  231. package/dist/suppression/index.d.ts +11 -0
  232. package/dist/suppression/index.d.ts.map +1 -0
  233. package/dist/suppression/index.js +39 -0
  234. package/dist/suppression/index.js.map +1 -0
  235. package/dist/suppression/inline-parser.d.ts +39 -0
  236. package/dist/suppression/inline-parser.d.ts.map +1 -0
  237. package/dist/suppression/inline-parser.js +218 -0
  238. package/dist/suppression/inline-parser.js.map +1 -0
  239. package/dist/suppression/manager.d.ts +94 -0
  240. package/dist/suppression/manager.d.ts.map +1 -0
  241. package/dist/suppression/manager.js +292 -0
  242. package/dist/suppression/manager.js.map +1 -0
  243. package/dist/suppression/types.d.ts +151 -0
  244. package/dist/suppression/types.d.ts.map +1 -0
  245. package/dist/suppression/types.js +28 -0
  246. package/dist/suppression/types.js.map +1 -0
  247. package/dist/tiers.d.ts +1 -1
  248. package/dist/tiers.d.ts.map +1 -1
  249. package/dist/tiers.js +27 -0
  250. package/dist/tiers.js.map +1 -1
  251. package/dist/types.d.ts +62 -1
  252. package/dist/types.d.ts.map +1 -1
  253. package/dist/types.js.map +1 -1
  254. package/dist/utils/context-helpers.d.ts +4 -0
  255. package/dist/utils/context-helpers.d.ts.map +1 -1
  256. package/dist/utils/context-helpers.js +13 -9
  257. package/dist/utils/context-helpers.js.map +1 -1
  258. package/package.json +4 -2
  259. package/src/__tests__/benchmark/fixtures/layer1/mcp-config-audit.json +31 -0
  260. package/src/__tests__/benchmark/fixtures/layer2/ai-execution-sinks.ts +1489 -82
  261. package/src/__tests__/benchmark/fixtures/layer2/ai-mcp-security.ts +495 -0
  262. package/src/__tests__/benchmark/fixtures/layer2/ai-package-hallucination.ts +255 -0
  263. package/src/__tests__/benchmark/fixtures/layer2/ai-prompt-hygiene.ts +300 -1
  264. package/src/__tests__/benchmark/fixtures/layer2/ai-rag-safety.ts +139 -0
  265. package/src/__tests__/benchmark/fixtures/layer2/byok-patterns.ts +7 -0
  266. package/src/__tests__/benchmark/fixtures/layer2/data-exposure.ts +63 -0
  267. package/src/__tests__/benchmark/fixtures/layer2/excessive-agency.ts +221 -0
  268. package/src/__tests__/benchmark/fixtures/layer2/index.ts +18 -0
  269. package/src/__tests__/benchmark/fixtures/layer2/model-supply-chain.ts +204 -0
  270. package/src/__tests__/benchmark/fixtures/layer2/phase1-enhancements.ts +157 -0
  271. package/src/__tests__/snapshots/__snapshots__/anthropic-validation-refactor.test.ts.snap +758 -0
  272. package/src/__tests__/snapshots/__snapshots__/dangerous-functions-refactor.test.ts.snap +503 -0
  273. package/src/__tests__/snapshots/anthropic-validation-refactor.test.ts +321 -0
  274. package/src/__tests__/snapshots/dangerous-functions-refactor.test.ts +439 -0
  275. package/src/baseline/__tests__/diff.test.ts +261 -0
  276. package/src/baseline/__tests__/manager.test.ts +225 -0
  277. package/src/baseline/diff.ts +135 -0
  278. package/src/baseline/index.ts +29 -0
  279. package/src/baseline/manager.ts +230 -0
  280. package/src/baseline/types.ts +97 -0
  281. package/src/formatters/cli-terminal.ts +444 -41
  282. package/src/formatters/github-comment.ts +79 -11
  283. package/src/formatters/index.ts +4 -0
  284. package/src/index.ts +197 -14
  285. package/src/layer1/config-audit.ts +24 -3
  286. package/src/layer1/config-mcp-audit.ts +276 -0
  287. package/src/layer1/index.ts +16 -6
  288. package/src/layer2/ai-agent-tools.ts +336 -0
  289. package/src/layer2/ai-endpoint-protection.ts +16 -3
  290. package/src/layer2/ai-execution-sinks.ts +516 -12
  291. package/src/layer2/ai-fingerprinting.ts +5 -1
  292. package/src/layer2/ai-mcp-security.ts +730 -0
  293. package/src/layer2/ai-package-hallucination.ts +791 -0
  294. package/src/layer2/ai-prompt-hygiene.ts +547 -9
  295. package/src/layer2/ai-rag-safety.ts +382 -3
  296. package/src/layer2/auth-antipatterns.ts +5 -0
  297. package/src/layer2/byok-patterns.ts +5 -1
  298. package/src/layer2/dangerous-functions/child-process.ts +98 -0
  299. package/src/layer2/dangerous-functions/dom-xss.ts +220 -0
  300. package/src/layer2/dangerous-functions/index.ts +949 -0
  301. package/src/layer2/dangerous-functions/json-parse.ts +385 -0
  302. package/src/layer2/dangerous-functions/math-random.ts +537 -0
  303. package/src/layer2/dangerous-functions/patterns.ts +174 -0
  304. package/src/layer2/dangerous-functions/request-validation.ts +145 -0
  305. package/src/layer2/dangerous-functions/utils/control-flow.ts +162 -0
  306. package/src/layer2/dangerous-functions/utils/helpers.ts +170 -0
  307. package/src/layer2/dangerous-functions/utils/index.ts +25 -0
  308. package/src/layer2/dangerous-functions/utils/schema-validation.ts +91 -0
  309. package/src/layer2/data-exposure.ts +5 -1
  310. package/src/layer2/framework-checks.ts +5 -0
  311. package/src/layer2/index.ts +63 -1
  312. package/src/layer2/logic-gates.ts +5 -0
  313. package/src/layer2/model-supply-chain.ts +456 -0
  314. package/src/layer2/risky-imports.ts +5 -0
  315. package/src/layer2/variables.ts +5 -0
  316. package/src/layer3/__tests__/osv-check.test.ts +384 -0
  317. package/src/layer3/anthropic/auto-dismiss.ts +212 -0
  318. package/src/layer3/anthropic/clients.ts +84 -0
  319. package/src/layer3/anthropic/index.ts +170 -0
  320. package/src/layer3/anthropic/prompts/index.ts +14 -0
  321. package/src/layer3/anthropic/prompts/semantic-analysis.ts +173 -0
  322. package/src/layer3/anthropic/prompts/validation.ts +419 -0
  323. package/src/layer3/anthropic/providers/anthropic.ts +310 -0
  324. package/src/layer3/anthropic/providers/index.ts +8 -0
  325. package/src/layer3/anthropic/providers/openai.ts +384 -0
  326. package/src/layer3/anthropic/request-builder.ts +150 -0
  327. package/src/layer3/anthropic/types.ts +148 -0
  328. package/src/layer3/anthropic/utils/index.ts +26 -0
  329. package/src/layer3/anthropic/utils/path-helpers.ts +68 -0
  330. package/src/layer3/anthropic/utils/response-parser.ts +322 -0
  331. package/src/layer3/anthropic/utils/retry.ts +75 -0
  332. package/src/layer3/index.ts +18 -5
  333. package/src/layer3/osv-check.ts +420 -0
  334. package/src/rules/__tests__/framework-fixes.test.ts +689 -0
  335. package/src/rules/__tests__/metadata.test.ts +218 -0
  336. package/src/rules/framework-fixes.ts +470 -0
  337. package/src/rules/index.ts +21 -0
  338. package/src/rules/metadata.ts +831 -0
  339. package/src/suppression/__tests__/config-loader.test.ts +382 -0
  340. package/src/suppression/__tests__/hash.test.ts +166 -0
  341. package/src/suppression/__tests__/inline-parser.test.ts +212 -0
  342. package/src/suppression/__tests__/manager.test.ts +415 -0
  343. package/src/suppression/config-loader.ts +462 -0
  344. package/src/suppression/hash.ts +95 -0
  345. package/src/suppression/index.ts +51 -0
  346. package/src/suppression/inline-parser.ts +273 -0
  347. package/src/suppression/manager.ts +379 -0
  348. package/src/suppression/types.ts +174 -0
  349. package/src/tiers.ts +36 -0
  350. package/src/types.ts +90 -0
  351. package/src/utils/context-helpers.ts +13 -9
  352. package/dist/layer2/dangerous-functions.d.ts +0 -7
  353. package/dist/layer2/dangerous-functions.d.ts.map +0 -1
  354. package/dist/layer2/dangerous-functions.js +0 -1701
  355. package/dist/layer2/dangerous-functions.js.map +0 -1
  356. package/dist/layer3/anthropic.d.ts +0 -87
  357. package/dist/layer3/anthropic.d.ts.map +0 -1
  358. package/dist/layer3/anthropic.js +0 -1948
  359. package/dist/layer3/anthropic.js.map +0 -1
  360. package/dist/layer3/openai.d.ts +0 -25
  361. package/dist/layer3/openai.d.ts.map +0 -1
  362. package/dist/layer3/openai.js +0 -238
  363. package/dist/layer3/openai.js.map +0 -1
  364. package/src/layer2/dangerous-functions.ts +0 -1940
  365. package/src/layer3/anthropic.ts +0 -2257
@@ -1,1701 +0,0 @@
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
- // NOTE: toString patterns removed - now handled by analyzeToStringPattern()
705
- // which provides more granular severity classification (info/low/medium/high)
706
- // based on truncation length and context
707
- ];
708
- if (cosmeticLinePatterns.some(p => p.test(lineContent))) {
709
- return true;
710
- }
711
- // Check surrounding context (5 lines before and after)
712
- const contextStart = Math.max(0, lineNumber - 5);
713
- const contextEnd = Math.min(lines.length, lineNumber + 5);
714
- const context = lines.slice(contextStart, contextEnd).join('\n');
715
- // Context indicators of cosmetic use
716
- const cosmeticContextPatterns = [
717
- // UI component files - REMOVED, let severity classification handle these
718
- // Style-related variables/functions
719
- /\b(style|styles|css|className|animation|transition)/i,
720
- /\b(width|height|opacity|color|transform|rotate|scale|translate)/i,
721
- // Demo/example data
722
- /\b(demo|example|placeholder|mock|fake|sample|test)Data/i,
723
- /\b(random|shuffle|pick|choose).*\b(color|item|element|option)/i,
724
- // Animation/timing
725
- /setTimeout.*Math\.random/i,
726
- /setInterval.*Math\.random/i,
727
- /delay.*Math\.random/i,
728
- /duration.*Math\.random/i,
729
- // UI state variations
730
- /\b(variant|theme|layout|position).*Math\.random/i,
731
- // NOTE: Removed UI identifier patterns (key, id, tempId, etc.) - these should be
732
- // classified with info/low severity by the severity classification logic, not skipped entirely
733
- ];
734
- if (cosmeticContextPatterns.some(p => p.test(context))) {
735
- return true;
736
- }
737
- // Security-sensitive patterns that override cosmetic detection
738
- const securityPatterns = [
739
- /\b(token|secret|key|password|credential|signature)/i,
740
- /\b(auth|crypto|encrypt|decrypt|hash)/i,
741
- /\b(session|nonce|salt)\b/i,
742
- /Math\.random.*\*\s*1e\d+/, // Math.random() * 1e16 (large numbers for IDs)
743
- ];
744
- if (securityPatterns.some(p => p.test(lineContent) || p.test(context))) {
745
- return false; // Not cosmetic - this is security-sensitive
746
- }
747
- // Check for .toString(36) WITHOUT substring/slice/substr (security token pattern)
748
- // If it has substring/slice/substr, it's already caught by cosmeticLinePatterns above
749
- const hasToString36WithoutTruncation = /Math\.random\(\)\.toString\(36\)/.test(lineContent) &&
750
- !/\.(substring|substr|slice)\(/.test(lineContent);
751
- const hasToString16WithoutTruncation = /Math\.random\(\)\.toString\(16\)/.test(lineContent) &&
752
- !/\.(substring|substr|slice)\(/.test(lineContent);
753
- if (hasToString36WithoutTruncation || hasToString16WithoutTruncation) {
754
- return false; // Full-length toString() without truncation - likely security token
755
- }
756
- return false; // Default to flagging if unclear
757
- }
758
- /**
759
- * Extract function context where Math.random() is being called
760
- * Looks backwards from the current line to find enclosing function name
761
- * Returns lowercase function name or null if not found
762
- */
763
- function extractFunctionContext(content, lineNumber) {
764
- const lines = content.split('\n');
765
- const start = Math.max(0, lineNumber - 20); // Increased from 10 to 20 for nested callbacks
766
- // Look backwards for function declaration
767
- for (let i = lineNumber; i >= start; i--) {
768
- const line = lines[i];
769
- // Skip anonymous arrow functions in callbacks (e.g., .map((x) => ...), .replace(/x/g, (c) => ...))
770
- // These are not the function context we're looking for
771
- // Look for pattern: .methodName(..., (param) => or .methodName(...(param) =>
772
- const hasMethodCallWithArrowCallback = /\.\w+\(.*\([^)]*\)\s*=>/.test(line);
773
- if (hasMethodCallWithArrowCallback) {
774
- continue; // Skip this line and keep looking
775
- }
776
- // Match various function declaration patterns
777
- // 1. function functionName
778
- // 2. export function functionName
779
- // 3. const/let functionName = function
780
- // 4. const/let functionName = (arrow function)
781
- // 5. export const functionName =
782
- // Traditional function declaration (handles TypeScript type annotations)
783
- // Matches: function name(...), export function name(...), function name<T>(...), etc.
784
- const funcDeclMatch = line.match(/(?:export\s+)?(?:async\s+)?function\s+(\w+)/i);
785
- if (funcDeclMatch) {
786
- return funcDeclMatch[1].toLowerCase();
787
- }
788
- // Function expression assignment (const foo = function...)
789
- const funcExprMatch = line.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s+)?function/i);
790
- if (funcExprMatch) {
791
- return funcExprMatch[1].toLowerCase();
792
- }
793
- // Arrow function assignment - require => to be present on the same line
794
- // This prevents matching "const r = (Math.random()..." as an arrow function
795
- if (line.includes('=>')) {
796
- const arrowFuncMatch = line.match(/(?:export\s+)?(?:const|let|var)\s+(\w+)\s*=/i);
797
- if (arrowFuncMatch) {
798
- return arrowFuncMatch[1].toLowerCase();
799
- }
800
- }
801
- }
802
- return null;
803
- }
804
- /**
805
- * Classify function intent based on function name
806
- * Used to determine if Math.random() usage is legitimate
807
- */
808
- function classifyFunctionIntent(functionName) {
809
- if (!functionName)
810
- return 'unknown';
811
- const lower = functionName.toLowerCase();
812
- // UUID/ID generation (UI correlation, not security)
813
- // Check for specific UUID patterns and generic ID generation functions
814
- const uuidPatterns = ['uuid', 'guid', 'uniqueid', 'correlationid'];
815
- const idGenerationPatterns = /^(generate|create|make|build)(id|identifier)$/i;
816
- if (uuidPatterns.some(p => lower.includes(p)) || idGenerationPatterns.test(lower)) {
817
- return 'uuid';
818
- }
819
- // CAPTCHA/puzzle generation (legitimate non-security)
820
- const captchaPatterns = ['captcha', 'puzzle', 'mathproblem'];
821
- // Also check for 'challenge' but only if not in security context
822
- if (captchaPatterns.some(p => lower.includes(p)))
823
- return 'captcha';
824
- if (lower.includes('challenge') && !lower.includes('auth'))
825
- return 'captcha';
826
- // Demo/seed/fixture data
827
- const demoPatterns = ['seed', 'fixture', 'demo', 'mock', 'fake'];
828
- if (demoPatterns.some(p => lower.includes(p)))
829
- return 'demo';
830
- // Security-sensitive (check this after id generation to avoid false positives)
831
- const securityPatterns = ['token', 'secret', 'key', 'password', 'credential', 'signature'];
832
- // Also match generate/create + security term combinations
833
- const securityFunctionPattern = /^(generate|create|make)(token|secret|key|session|password|credential)/i;
834
- if (securityPatterns.some(p => lower.includes(p)) || securityFunctionPattern.test(lower)) {
835
- return 'security';
836
- }
837
- return 'unknown';
838
- }
839
- /**
840
- * Analyze toString() pattern in Math.random() usage
841
- * Determines intent based on base and truncation length
842
- */
843
- function analyzeToStringPattern(lineContent) {
844
- const toString36Match = lineContent.match(/Math\.random\(\)\.toString\(36\)/);
845
- const toString16Match = lineContent.match(/Math\.random\(\)\.toString\(16\)/);
846
- if (!toString36Match && !toString16Match) {
847
- return { hasToString: false, base: null, isTruncated: false, truncationLength: null, intent: 'unknown' };
848
- }
849
- const base = toString36Match ? 36 : 16;
850
- // Check for truncation methods
851
- const substringMatch = lineContent.match(/\.substring\((\d+)(?:,\s*(\d+))?\)/);
852
- const sliceMatch = lineContent.match(/\.slice\((\d+)(?:,\s*(\d+))?\)/);
853
- const substrMatch = lineContent.match(/\.substr\((\d+)(?:,\s*(\d+))?\)/);
854
- const truncMatch = substringMatch || sliceMatch || substrMatch;
855
- if (!truncMatch) {
856
- return { hasToString: true, base, isTruncated: false, truncationLength: null, intent: 'full-token' };
857
- }
858
- // Calculate truncation length
859
- const start = parseInt(truncMatch[1]);
860
- const end = truncMatch[2] ? parseInt(truncMatch[2]) : null;
861
- const length = end ? (end - start) : null;
862
- // Classify intent by length
863
- // Short (2-9 chars): UI correlation IDs, React keys
864
- // Medium (10-15 chars): Business IDs, order numbers
865
- if (length && length <= 9) {
866
- return { hasToString: true, base, isTruncated: true, truncationLength: length, intent: 'short-ui-id' };
867
- }
868
- else if (length && length <= 15) {
869
- return { hasToString: true, base, isTruncated: true, truncationLength: length, intent: 'business-id' };
870
- }
871
- else {
872
- return { hasToString: true, base, isTruncated: true, truncationLength: length, intent: 'business-id' };
873
- }
874
- }
875
- /**
876
- * Extract variable name from Math.random() assignment
877
- * Examples:
878
- * const token = Math.random() -> "token"
879
- * const businessId = Math.random().toString(36) -> "businessId"
880
- * return Math.random() -> null (no variable)
881
- */
882
- function extractMathRandomVariableName(lineContent) {
883
- // const/let/var variableName = Math.random...
884
- const assignmentMatch = lineContent.match(/(?:const|let|var)\s+(\w+)\s*=.*Math\.random/);
885
- if (assignmentMatch)
886
- return assignmentMatch[1];
887
- // object.property = Math.random...
888
- const propertyMatch = lineContent.match(/(\w+)\s*[:=]\s*Math\.random/);
889
- if (propertyMatch)
890
- return propertyMatch[1];
891
- // function parameter default: functionName(param = Math.random())
892
- const paramMatch = lineContent.match(/(\w+)\s*=\s*Math\.random/);
893
- if (paramMatch)
894
- return paramMatch[1];
895
- return null; // No variable name found
896
- }
897
- /**
898
- * Classify variable name security risk based on naming patterns
899
- *
900
- * High risk: Security-sensitive names (token, secret, key, etc.)
901
- * Medium risk: Unclear context
902
- * Low risk: Non-security names (id, businessId, orderId, etc.)
903
- */
904
- function classifyVariableNameRisk(varName) {
905
- if (!varName)
906
- return 'medium'; // Unknown usage, moderate risk
907
- const lower = varName.toLowerCase();
908
- // High risk: security-sensitive variable names
909
- const highRiskPatterns = [
910
- 'token', 'secret', 'key', 'password', 'credential',
911
- 'signature', 'salt', 'nonce', 'session', 'csrf',
912
- 'auth', 'apikey', 'accesstoken', 'refreshtoken',
913
- 'jwt', 'bearer', 'oauth', 'sessionid'
914
- ];
915
- if (highRiskPatterns.some(p => lower.includes(p))) {
916
- return 'high';
917
- }
918
- // Low risk: clearly non-security contexts
919
- const lowRiskPatterns = [
920
- // Business identifiers
921
- 'id', 'uid', 'guid', 'business', 'order', 'invoice',
922
- 'customer', 'user', 'product', 'item', 'transaction',
923
- 'request', 'reference', 'tracking', 'confirmation',
924
- // Test/demo data
925
- 'test', 'mock', 'demo', 'sample', 'example', 'fixture',
926
- 'random', 'temp', 'temporary', 'generated', 'dummy',
927
- // UI identifiers
928
- 'toast', 'notification', 'element', 'component', 'widget',
929
- 'modal', 'dialog', 'popup', 'unique', 'react',
930
- // Non-security randomness usage (backoff/sampling/experiments)
931
- 'jitter', 'retry', 'backoff', 'delay', 'timeout', 'latency',
932
- 'sample', 'sampling', 'probability', 'chance', 'rollout',
933
- 'experiment', 'abtest', 'cohort', 'bucket', 'variant'
934
- ];
935
- if (lowRiskPatterns.some(p => lower.includes(p))) {
936
- return 'low';
937
- }
938
- return 'medium'; // Unclear context, moderate risk
939
- }
940
- /**
941
- * Analyze surrounding code context for security signals
942
- * Returns context type and description for severity classification
943
- */
944
- function analyzeMathRandomContext(content, filePath, lineNumber) {
945
- const lines = content.split('\n');
946
- const start = Math.max(0, lineNumber - 10);
947
- const end = Math.min(lines.length, lineNumber + 5);
948
- const context = lines.slice(start, end).join('\n');
949
- // Security context indicators (functions, imports, comments)
950
- const securityPatterns = [
951
- /\b(generate|create)(Token|Secret|Key|Password|Nonce|Salt|Session|Signature)/i,
952
- /\b(auth|crypto|encrypt|decrypt|hash|sign)\b/i,
953
- /function\s+.*(?:token|secret|key|auth|crypto)/i,
954
- /\bimport.*(?:crypto|jsonwebtoken|bcrypt|argon2|jose)/i,
955
- /\/\*.*(?:security|authentication|cryptograph|authorization)/i,
956
- /\/\/.*(?:security|auth|crypto|token|secret)/i,
957
- ];
958
- const inSecurityContext = securityPatterns.some(p => p.test(context));
959
- // Test context
960
- const testFilePatterns = /\.(test|spec)\.(ts|tsx|js|jsx)$/i;
961
- const testContextPatterns = [
962
- /\b(describe|it|test|expect|mock|jest|vitest|mocha|chai)\b/i,
963
- /\b(beforeEach|afterEach|beforeAll|afterAll)\b/i,
964
- /\b(fixture|stub|spy)\b/i,
965
- ];
966
- const inTestContext = testFilePatterns.test(filePath) ||
967
- testContextPatterns.some(p => p.test(context));
968
- // UI/cosmetic context (reuse existing logic)
969
- const lineContent = lines[lineNumber];
970
- const inUIContext = isCosmeticMathRandom(lineContent, content, lineNumber);
971
- // Business logic context (non-security ID generation)
972
- // Note: UUID/CAPTCHA patterns excluded - handled by functionIntent classification
973
- const businessLogicPatterns = [
974
- /\b(business|order|invoice|customer|product|transaction)Id\b/i,
975
- /\b(reference|tracking|confirmation)Number\b/i,
976
- /\b(backoff|retry|jitter|delay|timeout|latency)\b/i,
977
- /\b(sample|sampling|probability|chance|rollout|experiment|abtest|cohort|bucket|variant)\b/i,
978
- ];
979
- const inBusinessLogicContext = businessLogicPatterns.some(p => p.test(context)) &&
980
- !inSecurityContext;
981
- // Determine context description
982
- let contextDescription = 'unknown context';
983
- if (inSecurityContext) {
984
- contextDescription = 'security-sensitive function';
985
- }
986
- else if (inTestContext) {
987
- contextDescription = 'test/mock data generation';
988
- }
989
- else if (inUIContext) {
990
- contextDescription = 'UI/cosmetic usage';
991
- }
992
- else if (inBusinessLogicContext) {
993
- contextDescription = 'non-security usage';
994
- }
995
- return {
996
- inSecurityContext,
997
- inTestContext,
998
- inUIContext,
999
- inBusinessLogicContext,
1000
- contextDescription,
1001
- };
1002
- }
1003
- function detectDangerousFunctions(content, filePath) {
1004
- const vulnerabilities = [];
1005
- // Skip scanner/fixture files to avoid self-detection
1006
- if ((0, context_helpers_1.isScannerOrFixtureFile)(filePath)) {
1007
- return vulnerabilities;
1008
- }
1009
- const lines = content.split('\n');
1010
- const isTestFile = (0, context_helpers_1.isTestOrMockFile)(filePath);
1011
- lines.forEach((line, index) => {
1012
- // Skip comment lines
1013
- if ((0, context_helpers_1.isComment)(line))
1014
- return;
1015
- for (const funcPattern of DANGEROUS_FUNCTIONS) {
1016
- // Check language filter
1017
- if (!matchesLanguage(filePath, funcPattern.languages))
1018
- continue;
1019
- const regex = new RegExp(funcPattern.pattern.source, funcPattern.pattern.flags);
1020
- if (regex.test(line)) {
1021
- // Special handling for innerHTML patterns
1022
- if (funcPattern.name === 'innerHTML assignment' ||
1023
- funcPattern.name === 'dangerouslySetInnerHTML') {
1024
- // Check if this uses static content only
1025
- if (isStaticHTMLContent(line, content, index)) {
1026
- vulnerabilities.push({
1027
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1028
- filePath,
1029
- lineNumber: index + 1,
1030
- lineContent: line.trim(),
1031
- severity: 'info',
1032
- category: 'dangerous_function',
1033
- title: funcPattern.name + ' (static content)',
1034
- description: 'Static HTML assignment detected. Generally safe for hardcoded content, but consider using textContent for plain text or proper DOM methods for dynamic content.',
1035
- 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.',
1036
- confidence: 'low',
1037
- layer: 2,
1038
- });
1039
- break; // Only report once per line
1040
- }
1041
- // Check if DOMPurify or similar sanitization is used
1042
- if (hasDOMPurifySanitization(line, content, index)) {
1043
- vulnerabilities.push({
1044
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1045
- filePath,
1046
- lineNumber: index + 1,
1047
- lineContent: line.trim(),
1048
- severity: 'info',
1049
- category: 'dangerous_function',
1050
- title: funcPattern.name + ' (sanitized)',
1051
- description: 'HTML is sanitized before rendering (DOMPurify or similar detected). This is the recommended pattern for rendering user-generated HTML.',
1052
- suggestedFix: 'Ensure DOMPurify is configured correctly and kept up to date.',
1053
- confidence: 'low',
1054
- layer: 2,
1055
- });
1056
- break; // Only report once per line
1057
- }
1058
- // Check if this is a static bootstrap script (e.g., theme/font loader)
1059
- if (isStaticBootstrapScript(line, content, index)) {
1060
- vulnerabilities.push({
1061
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1062
- filePath,
1063
- lineNumber: index + 1,
1064
- lineContent: line.trim(),
1065
- severity: 'info',
1066
- category: 'dangerous_function',
1067
- title: funcPattern.name + ' (static bootstrap script)',
1068
- 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.',
1069
- suggestedFix: 'Verify no user-controlled data is interpolated into the script content.',
1070
- confidence: 'low',
1071
- layer: 2,
1072
- });
1073
- break; // Only report once per line
1074
- }
1075
- // Check if this is in LLM prompt context (not XSS - it's prompt injection)
1076
- if (isLLMPromptContext(line, content, filePath)) {
1077
- vulnerabilities.push({
1078
- id: `dangerous-func-${filePath}-${index + 1}-prompt-injection`,
1079
- filePath,
1080
- lineNumber: index + 1,
1081
- lineContent: line.trim(),
1082
- severity: 'info',
1083
- category: 'ai_pattern',
1084
- title: 'Potential prompt injection risk',
1085
- 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.',
1086
- suggestedFix: 'Consider input validation, content filtering, or structured prompts to limit prompt injection risk.',
1087
- confidence: 'low',
1088
- layer: 2,
1089
- });
1090
- break; // Only report once per line
1091
- }
1092
- // Dynamic content - full severity, needs AI validation
1093
- let severity = funcPattern.severity;
1094
- if (isTestFile) {
1095
- severity = 'low';
1096
- }
1097
- vulnerabilities.push({
1098
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1099
- filePath,
1100
- lineNumber: index + 1,
1101
- lineContent: line.trim(),
1102
- severity,
1103
- category: 'dangerous_function',
1104
- title: funcPattern.name,
1105
- description: funcPattern.description + ' This appears to use dynamic content which increases XSS risk.' + (isTestFile ? ' (in test file)' : ''),
1106
- suggestedFix: funcPattern.suggestedFix,
1107
- confidence: isTestFile ? 'low' : 'high',
1108
- layer: 2,
1109
- requiresAIValidation: true, // Dynamic HTML needs validation
1110
- });
1111
- break; // Only report once per line
1112
- }
1113
- // Note: JSON.parse is now handled by standalone detectJSONParseSafe() function
1114
- // which provides better source-aware severity classification
1115
- // Special handling for eval and Function constructor
1116
- if (funcPattern.name === 'eval() usage' || funcPattern.name === 'Function constructor') {
1117
- // Suppress entirely in test files - test files legitimately test eval behavior
1118
- if (isTestFile) {
1119
- break; // Skip reporting entirely
1120
- }
1121
- // Check if eval is inside a test assertion (expect(), test(), it(), describe())
1122
- const testAssertionPattern = /\b(expect|test|it|describe)\s*\(/;
1123
- if (testAssertionPattern.test(line)) {
1124
- break; // Skip reporting - this is testing eval behavior
1125
- }
1126
- // Check if inputs are static literals (low risk)
1127
- if (hasOnlyStaticInputs(line, content, index)) {
1128
- vulnerabilities.push({
1129
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1130
- filePath,
1131
- lineNumber: index + 1,
1132
- lineContent: line.trim(),
1133
- severity: 'info',
1134
- category: 'dangerous_function',
1135
- title: funcPattern.name + ' (static input)',
1136
- description: 'eval/Function with static string literal input. Lower risk than dynamic input, but consider refactoring to avoid eval entirely.',
1137
- suggestedFix: 'Consider using JSON.parse() for JSON data or refactoring to avoid eval.',
1138
- confidence: 'low',
1139
- layer: 2,
1140
- });
1141
- break;
1142
- }
1143
- vulnerabilities.push({
1144
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1145
- filePath,
1146
- lineNumber: index + 1,
1147
- lineContent: line.trim(),
1148
- severity: funcPattern.severity,
1149
- category: 'dangerous_function',
1150
- title: funcPattern.name,
1151
- description: funcPattern.description,
1152
- suggestedFix: funcPattern.suggestedFix,
1153
- confidence: 'high',
1154
- layer: 2,
1155
- requiresAIValidation: true, // Code execution patterns need validation
1156
- });
1157
- break;
1158
- }
1159
- // Special handling for child_process exec - verify it's not RegExp.exec
1160
- if (funcPattern.name === 'child_process exec') {
1161
- // First check if this is actually from child_process (not RegExp.exec)
1162
- const isExecMatch = /\bexec\s*\(/.test(line);
1163
- const isOtherMatch = /\b(execSync|spawn|spawnSync|execFile)\s*\(/.test(line);
1164
- if (isExecMatch && !isOtherMatch) {
1165
- // This matched 'exec(' - verify it's from child_process
1166
- if (!isChildProcessExec(content, line)) {
1167
- // This is RegExp.exec or similar - skip
1168
- break;
1169
- }
1170
- }
1171
- else if (isOtherMatch) {
1172
- // This matched spawn/execSync/etc - verify child_process import
1173
- if (!isChildProcessSpawn(content, line)) {
1174
- // No child_process import - skip
1175
- break;
1176
- }
1177
- }
1178
- if (hasOnlyStaticInputs(line, content, index)) {
1179
- vulnerabilities.push({
1180
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1181
- filePath,
1182
- lineNumber: index + 1,
1183
- lineContent: line.trim(),
1184
- severity: 'info',
1185
- category: 'dangerous_function',
1186
- title: funcPattern.name + ' (static command)',
1187
- description: 'exec/execSync with hardcoded command. Lower risk than dynamic commands, but ensure command does not change based on user input.',
1188
- suggestedFix: 'If command is truly static, this is generally acceptable. For dynamic commands, validate and sanitize inputs.',
1189
- confidence: 'low',
1190
- layer: 2,
1191
- });
1192
- break;
1193
- }
1194
- }
1195
- // Special handling for SQL patterns - check for whitelist validation
1196
- if (funcPattern.name === 'Raw SQL query construction' || funcPattern.name === 'SQL template literal') {
1197
- if (hasSQLWhitelistValidation(content, index)) {
1198
- vulnerabilities.push({
1199
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1200
- filePath,
1201
- lineNumber: index + 1,
1202
- lineContent: line.trim(),
1203
- severity: 'info',
1204
- category: 'dangerous_function',
1205
- title: funcPattern.name + ' (whitelist validated)',
1206
- description: 'SQL query with dynamic content, but whitelist/allowlist validation detected. This is a safer pattern that limits injection risk.',
1207
- suggestedFix: 'Ensure the whitelist is comprehensive and cannot be bypassed. Consider using parameterized queries for additional safety.',
1208
- confidence: 'low',
1209
- layer: 2,
1210
- });
1211
- break;
1212
- }
1213
- }
1214
- // Special handling for Dynamic file path - skip for utility files
1215
- // Utility functions designed to work with file paths are expected to take path parameters
1216
- if (funcPattern.name === 'Dynamic file path') {
1217
- // Skip for utility/lib/helper files - these are internal functions, not API handlers
1218
- const isUtilityFile = /\/(utils?|lib|helpers?|services?|modules?)\//i.test(filePath);
1219
- // Skip if function name suggests it's designed for file operations
1220
- 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));
1221
- // Skip CLI command files - these take paths from command-line args (controlled inputs)
1222
- const isCLIFile = /\/(cli|commands?|bin)\//i.test(filePath) ||
1223
- /\/src\/(index|main|cli)\.(ts|js)$/i.test(filePath);
1224
- // Skip GitHub Action files - these process repo files (controlled environment)
1225
- const isGitHubAction = /github-action/i.test(filePath) ||
1226
- /action\.(ts|js|yml|yaml)$/i.test(filePath);
1227
- // Check for schema validation patterns in the surrounding context
1228
- // Zod, Yup, Joi, or regex validation on the input
1229
- const contextWindow = content.slice(Math.max(0, content.indexOf(line) - 500), content.indexOf(line) + line.length);
1230
- const hasSchemaValidation = /z\.(string|object)\s*\(\s*\)\.regex\s*\(/i.test(contextWindow) ||
1231
- /z\.enum\s*\(/i.test(contextWindow) ||
1232
- /\.regex\s*\(\s*\/.*\/\s*\)/i.test(contextWindow) || // .regex(/.../)
1233
- /\.match\s*\(\s*\/.*\/\s*\)/i.test(contextWindow) || // .match(/.../)
1234
- /\.(schema|validate)\s*\(/i.test(contextWindow) ||
1235
- /joi\./i.test(contextWindow) ||
1236
- /yup\./i.test(contextWindow);
1237
- // Check for path sanitization patterns
1238
- const hasPathSanitization = hasPathTraversalProtection(contextWindow, line);
1239
- if (isUtilityFile || isFileOperationFunction || isTestFile || isCLIFile || isGitHubAction || hasSchemaValidation || hasPathSanitization) {
1240
- // Skip entirely for utility functions or when schema validation is present
1241
- break;
1242
- }
1243
- }
1244
- // Special handling for Path traversal risk - check for sanitization
1245
- if (funcPattern.name === 'Path traversal risk') {
1246
- const contextWindow = content.slice(Math.max(0, content.indexOf(line) - 500), content.indexOf(line) + line.length + 200);
1247
- // Check for path sanitization patterns
1248
- if (hasPathTraversalProtection(contextWindow, line)) {
1249
- vulnerabilities.push({
1250
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1251
- filePath,
1252
- lineNumber: index + 1,
1253
- lineContent: line.trim(),
1254
- severity: 'info',
1255
- category: 'dangerous_function',
1256
- title: funcPattern.name + ' (sanitized)',
1257
- description: 'User input in file path, but path traversal protection detected. Verify sanitization is comprehensive.',
1258
- suggestedFix: 'Ensure path.resolve() result is checked against base directory and ".." sequences are rejected.',
1259
- confidence: 'low',
1260
- layer: 2,
1261
- });
1262
- break;
1263
- }
1264
- }
1265
- // Special handling for Math.random() - enhanced context-aware severity classification
1266
- if (funcPattern.name === 'Math.random for security') {
1267
- // Phase 1: File-level exclusions (skip entirely)
1268
- if ((0, context_helpers_1.isSeedOrDataGenFile)(filePath)) {
1269
- break; // Skip seed/data generation files entirely
1270
- }
1271
- if ((0, context_helpers_1.isEducationalVulnerabilityFile)(filePath)) {
1272
- break; // Skip intentional vulnerability examples
1273
- }
1274
- // Phase 2: Context analysis
1275
- const varName = extractMathRandomVariableName(line);
1276
- const nameRisk = classifyVariableNameRisk(varName);
1277
- const context = analyzeMathRandomContext(content, filePath, index);
1278
- const functionName = extractFunctionContext(content, index);
1279
- const functionIntent = classifyFunctionIntent(functionName);
1280
- const toStringPattern = analyzeToStringPattern(line);
1281
- // Phase 3: Skip cosmetic/UI uses
1282
- if (context.inUIContext) {
1283
- break; // Already working
1284
- }
1285
- // Phase 4: Skip UUID/CAPTCHA generation functions
1286
- if (functionIntent === 'uuid' || functionIntent === 'captcha') {
1287
- break; // Legitimate non-security uses
1288
- }
1289
- // Phase 5: Determine severity
1290
- let severity = 'medium';
1291
- let confidence = 'medium';
1292
- let explanation = '';
1293
- let description = funcPattern.description;
1294
- let suggestedFix = funcPattern.suggestedFix;
1295
- // Test context - INFO
1296
- if (context.inTestContext) {
1297
- severity = 'info';
1298
- confidence = 'low';
1299
- explanation = ' (test data generation)';
1300
- description = 'Math.random() used in test context for generating mock data. Not security-critical, but consider crypto.randomUUID() for better uniqueness in tests.';
1301
- suggestedFix = 'Consider crypto.randomUUID() for test data uniqueness, though Math.random() is acceptable in tests';
1302
- }
1303
- // Seed/demo function context - INFO
1304
- else if (functionIntent === 'demo') {
1305
- severity = 'info';
1306
- confidence = 'low';
1307
- explanation = ' (seed/demo data generation)';
1308
- description = 'Math.random() used for generating fixture/seed data. Not security-critical in development contexts.';
1309
- suggestedFix = 'Acceptable for seed data. Use crypto.randomUUID() if uniqueness guarantees needed.';
1310
- }
1311
- // Short UI ID pattern - INFO (check before variable name to avoid false positives)
1312
- // e.g., "const key = Math.random().toString(36).substring(2, 9)" is a UI ID, not a security key
1313
- else if (toStringPattern.intent === 'short-ui-id') {
1314
- severity = 'info';
1315
- confidence = 'low';
1316
- explanation = ' (UI correlation ID)';
1317
- description = 'Math.random() used for short UI correlation IDs. Not security-critical, but collisions possible in high-volume scenarios.';
1318
- suggestedFix = 'For UI correlation, crypto.randomUUID() provides better uniqueness guarantees';
1319
- }
1320
- // Security context - HIGH
1321
- else if (nameRisk === 'high' || context.inSecurityContext || functionIntent === 'security') {
1322
- severity = 'high';
1323
- confidence = 'high';
1324
- explanation = ' (security-sensitive context)';
1325
- description = 'Math.random() is NOT cryptographically secure and MUST NOT be used for tokens, keys, passwords, or session IDs. This can lead to predictable values that attackers can exploit.';
1326
- suggestedFix = 'Replace with crypto.randomBytes() or crypto.randomUUID() for security-sensitive operations';
1327
- }
1328
- // Business/non-security pattern - LOW
1329
- else if (nameRisk === 'low' || context.inBusinessLogicContext || toStringPattern.intent === 'business-id') {
1330
- severity = 'low';
1331
- confidence = 'low';
1332
- explanation = ' (non-security usage)';
1333
- description = 'Math.random() is being used for non-security purposes (business IDs, sampling, jitter/backoff, experiments). While not critical, Math.random() can produce collisions or bias in high-volume scenarios.';
1334
- suggestedFix = 'Use crypto.randomUUID() for uniqueness-sensitive IDs. For sampling/backoff, consider a seeded PRNG if determinism is needed.';
1335
- }
1336
- // Unknown context - MEDIUM
1337
- else {
1338
- severity = 'medium';
1339
- confidence = 'medium';
1340
- explanation = ' (unclear context)';
1341
- description = 'Math.random() is being used. Verify this is not for security-critical purposes like tokens, session IDs, or cryptographic operations.';
1342
- suggestedFix = 'If used for security, replace with crypto.randomBytes(). For unique IDs, use crypto.randomUUID()';
1343
- }
1344
- // Update title with context
1345
- const title = `Math.random() in ${context.contextDescription}${explanation}`;
1346
- vulnerabilities.push({
1347
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1348
- filePath,
1349
- lineNumber: index + 1,
1350
- lineContent: line.trim(),
1351
- severity,
1352
- category: 'dangerous_function',
1353
- title,
1354
- description,
1355
- suggestedFix,
1356
- confidence,
1357
- layer: 2,
1358
- });
1359
- break; // Only report once per line
1360
- }
1361
- // Standard handling for all other patterns
1362
- let severity = funcPattern.severity;
1363
- let confidence = 'high';
1364
- if (isTestFile) {
1365
- if (severity === 'critical') {
1366
- severity = 'medium';
1367
- }
1368
- else if (severity === 'high') {
1369
- severity = 'low';
1370
- }
1371
- else {
1372
- severity = 'info';
1373
- }
1374
- confidence = 'low';
1375
- }
1376
- vulnerabilities.push({
1377
- id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
1378
- filePath,
1379
- lineNumber: index + 1,
1380
- lineContent: line.trim(),
1381
- severity,
1382
- category: 'dangerous_function',
1383
- title: funcPattern.name,
1384
- description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
1385
- suggestedFix: funcPattern.suggestedFix,
1386
- confidence,
1387
- layer: 2,
1388
- });
1389
- break; // Only report once per line
1390
- }
1391
- }
1392
- });
1393
- // Additional standalone checks (not in DANGEROUS_FUNCTIONS array)
1394
- // JSON.parse source-aware detection
1395
- detectJSONParseSafe(content, filePath, isTestFile, vulnerabilities);
1396
- // request.json() / req.json() schema validation suggestion
1397
- detectRequestJsonValidation(content, filePath, isTestFile, vulnerabilities);
1398
- return vulnerabilities;
1399
- }
1400
- /**
1401
- * Detect JSON.parse usage with source-aware severity
1402
- * Much smarter than simple pattern matching - considers try/catch and data source
1403
- */
1404
- function detectJSONParseSafe(content, filePath, isTestFile, vulnerabilities) {
1405
- const lines = content.split('\n');
1406
- const jsonParsePattern = /JSON\.parse\s*\(/gi;
1407
- // Track instances per file to aggregate noisy patterns
1408
- const instances = [];
1409
- lines.forEach((line, index) => {
1410
- if ((0, context_helpers_1.isComment)(line))
1411
- return;
1412
- jsonParsePattern.lastIndex = 0;
1413
- if (!jsonParsePattern.test(line))
1414
- return;
1415
- const jsonSource = classifyJSONParseSource(line, filePath);
1416
- // Skip migration files entirely - they're internal tooling
1417
- if (jsonSource === 'migration')
1418
- return;
1419
- // Skip test fixtures entirely - they're intentionally parsing test data
1420
- if (jsonSource === 'test_fixture')
1421
- return;
1422
- // Skip trusted SDK responses - these are well-defined and safe to parse
1423
- if (isTrustedSDKResponse(line, content))
1424
- return;
1425
- // Check if JSON.parse is inside a try-catch block
1426
- const insideTryCatch = isInsideTryCatch(content, index) || hasTryCatchNearby(content, index);
1427
- // Check if schema validation is applied after JSON.parse
1428
- const hasSchemaValidation = hasSchemaValidationNearby(content, index);
1429
- // If inside try-catch with safe source, suppress entirely - this is perfectly fine
1430
- if (insideTryCatch && ['local_storage', 'database', 'config', 'internal', 'ui_state'].includes(jsonSource)) {
1431
- return;
1432
- }
1433
- // If schema validation is present, this is properly handled
1434
- if (hasSchemaValidation) {
1435
- return;
1436
- }
1437
- // UI state (settings, providers, modals) - very low risk, aggregate or skip
1438
- if (jsonSource === 'ui_state') {
1439
- // Only track for aggregation, don't report individually
1440
- instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1441
- return;
1442
- }
1443
- // Determine severity based on source and error handling
1444
- let severity;
1445
- let description;
1446
- let suggestedFix;
1447
- let confidence = 'medium';
1448
- if (insideTryCatch) {
1449
- // Already has error handling
1450
- switch (jsonSource) {
1451
- case 'user_input':
1452
- severity = 'low';
1453
- description = 'JSON.parse on user input is wrapped in try-catch. Consider adding schema validation (zod/yup) to validate the parsed structure.';
1454
- suggestedFix = 'Add schema validation after parsing: const validated = schema.parse(JSON.parse(input))';
1455
- confidence = 'low';
1456
- break;
1457
- default:
1458
- // With try-catch and non-user source, this is fine - don't report
1459
- return;
1460
- }
1461
- }
1462
- else {
1463
- // No try-catch
1464
- switch (jsonSource) {
1465
- case 'user_input':
1466
- severity = 'medium';
1467
- description = 'JSON.parse on user input without schema validation. Malformed input will crash; malicious input may have unexpected shape.';
1468
- suggestedFix = 'Use a schema validation library (zod, yup, joi): try { const data = schema.parse(JSON.parse(body)) } catch (e) { return 400 }';
1469
- confidence = 'high';
1470
- break;
1471
- case 'local_storage':
1472
- severity = 'info';
1473
- description = 'JSON.parse on localStorage data. Consider adding try-catch for robustness against corrupted data.';
1474
- suggestedFix = 'Wrap in try-catch to handle corrupted localStorage gracefully.';
1475
- confidence = 'low';
1476
- break;
1477
- case 'database':
1478
- // Database content parsing is very common and low-risk
1479
- instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1480
- return; // Will be aggregated below
1481
- case 'config':
1482
- case 'internal':
1483
- severity = 'info';
1484
- description = `JSON.parse on ${jsonSource.replace('_', ' ')} data without error handling. Low risk but consider defensive coding.`;
1485
- suggestedFix = 'Consider adding try-catch for robustness.';
1486
- confidence = 'low';
1487
- break;
1488
- default:
1489
- // Unknown source - track for potential aggregation
1490
- instances.push({ lineNumber: index + 1, lineContent: line.trim(), source: jsonSource });
1491
- return; // Will be evaluated below based on aggregation
1492
- }
1493
- }
1494
- // Downgrade test files
1495
- if (isTestFile) {
1496
- severity = 'info';
1497
- confidence = 'low';
1498
- description += ' (in test file)';
1499
- }
1500
- vulnerabilities.push({
1501
- id: `json-parse-${filePath}-${index + 1}`,
1502
- filePath,
1503
- lineNumber: index + 1,
1504
- lineContent: line.trim(),
1505
- severity,
1506
- category: 'dangerous_function',
1507
- title: 'JSON.parse usage',
1508
- description,
1509
- suggestedFix,
1510
- confidence,
1511
- layer: 2,
1512
- });
1513
- });
1514
- // Aggregate low-risk JSON.parse instances if there are many
1515
- if (instances.length >= 3) {
1516
- // Create single aggregated finding instead of N individual findings
1517
- const lineNumbers = instances.map(i => i.lineNumber).slice(0, 5);
1518
- const moreText = instances.length > 5 ? `... (${instances.length} total)` : '';
1519
- vulnerabilities.push({
1520
- id: `json-parse-aggregated-${filePath}`,
1521
- filePath,
1522
- lineNumber: instances[0].lineNumber,
1523
- lineContent: `${instances.length} instances across this file`,
1524
- severity: 'info',
1525
- category: 'dangerous_function',
1526
- title: `JSON.parse usage (${instances.length} instances)`,
1527
- 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}`,
1528
- suggestedFix: 'Add try-catch for error handling. If parsing user input, add schema validation.',
1529
- confidence: 'low',
1530
- layer: 2,
1531
- });
1532
- }
1533
- else if (instances.length > 0 && instances.length < 3) {
1534
- // Report individually for small counts
1535
- for (const instance of instances) {
1536
- vulnerabilities.push({
1537
- id: `json-parse-${filePath}-${instance.lineNumber}`,
1538
- filePath,
1539
- lineNumber: instance.lineNumber,
1540
- lineContent: instance.lineContent,
1541
- severity: 'info',
1542
- category: 'dangerous_function',
1543
- title: 'JSON.parse usage',
1544
- description: `JSON.parse on ${instance.source.replace('_', ' ')} data without error handling. Low risk but consider defensive coding.${isTestFile ? ' (in test file)' : ''}`,
1545
- suggestedFix: 'Consider adding try-catch for robustness.',
1546
- confidence: 'low',
1547
- layer: 2,
1548
- });
1549
- }
1550
- }
1551
- }
1552
- /**
1553
- * Check if this file appears to have form/input validation elsewhere
1554
- * (manual checks on body fields, type guards, etc.)
1555
- */
1556
- function hasManualValidation(content) {
1557
- const manualValidationPatterns = [
1558
- // Type checking / type guards
1559
- /typeof\s+\w+\s*[!=]==?\s*['"](?:string|number|boolean|object)['"]|Array\.isArray\s*\(/i,
1560
- // Field existence checks followed by throws/returns
1561
- /if\s*\(\s*!(?:body|data|input)\.\w+\s*\)\s*\{?\s*(throw|return)/i,
1562
- // Property access with type assertion comments or inline validation
1563
- /\b(body|data|input)\s*as\s+\w+/i, // Type assertion
1564
- // Manual validation with error handling
1565
- /if\s*\(\s*![\w.]+\s*\|\|\s*typeof\s+[\w.]+/i,
1566
- // Using type predicates
1567
- /is\w+\s*\([\w.]+\)/i, // isFoo(bar) pattern
1568
- ];
1569
- return manualValidationPatterns.some(p => p.test(content));
1570
- }
1571
- /**
1572
- * Check if route has throwing auth helper (getCurrentUserId, requireAuth, etc.)
1573
- * Routes with throwing auth helpers are already protected
1574
- */
1575
- function hasThrowingAuthHelper(content) {
1576
- const throwingAuthPatterns = [
1577
- /\bgetCurrentUserId\s*\(/i,
1578
- /\brequireAuth\s*\(/i,
1579
- /\bensureAuth\s*\(/i,
1580
- /\bauth\s*\(\s*\)\s*\.protect\s*\(/i, // Clerk: auth().protect()
1581
- /\bcurrentUser\s*\(\s*\)/i, // Clerk: currentUser()
1582
- /\bgetServerSession\s*\([^)]*\)/i, // NextAuth
1583
- /\bauth\s*\(\s*\)/i, // Generic auth() call
1584
- /\bcheckAuth\s*\(/i,
1585
- /\bverifyAuth\s*\(/i,
1586
- /\bvalidateAuth\s*\(/i,
1587
- /\bassertAuth\s*\(/i,
1588
- /\bgetAuth\s*\(/i,
1589
- /\brequireUser\s*\(/i,
1590
- /\bgetUser\s*\(\s*\)/i, // supabase.auth.getUser()
1591
- /const\s+\{\s*user\s*\}\s*=\s*await/i, // Destructuring pattern
1592
- ];
1593
- return throwingAuthPatterns.some(p => p.test(content));
1594
- }
1595
- /**
1596
- * Detect request.json() / req.json() and suggest schema validation
1597
- * This is NOT a dangerous function - it's a prompt for best practices
1598
- */
1599
- function detectRequestJsonValidation(content, filePath, isTestFile, vulnerabilities) {
1600
- // Only check API route files
1601
- if (!/\/(api|routes?|handlers?|controllers?)\//i.test(filePath) &&
1602
- !/route\.(ts|js)$/i.test(filePath)) {
1603
- return;
1604
- }
1605
- // Skip if route has throwing auth helper - these are already protected routes
1606
- // and the schema validation suggestion is lower priority
1607
- if (hasThrowingAuthHelper(content)) {
1608
- return;
1609
- }
1610
- const lines = content.split('\n');
1611
- // Matches: request.json(), req.json(), await request.json(), etc.
1612
- const requestJsonPattern = /\b(request|req)\.json\s*\(\s*\)/gi;
1613
- // Check if file has schema validation (library-based)
1614
- const hasSchemaLibrary = /\b(zod|yup|joi|ajv|superstruct|valibot|typebox)\b/i.test(content) ||
1615
- /\.parse\s*\(|\.validate\s*\(|\.safeParse\s*\(/i.test(content);
1616
- // If file has schema library validation, don't report
1617
- if (hasSchemaLibrary)
1618
- return;
1619
- // Check for manual validation patterns (less robust but still indicates intent)
1620
- const hasManualCheck = hasManualValidation(content);
1621
- // Track instances for potential aggregation
1622
- const instances = [];
1623
- lines.forEach((line, index) => {
1624
- if ((0, context_helpers_1.isComment)(line))
1625
- return;
1626
- requestJsonPattern.lastIndex = 0;
1627
- if (!requestJsonPattern.test(line))
1628
- return;
1629
- // Check if there's validation nearby (within 10 lines after)
1630
- const startCheck = index;
1631
- const endCheck = Math.min(lines.length, index + 10);
1632
- const nearbyContent = lines.slice(startCheck, endCheck).join('\n');
1633
- // If there's validation in the nearby lines, skip
1634
- if (/\.parse\s*\(|\.validate\s*\(|\.safeParse\s*\(|schema\./i.test(nearbyContent)) {
1635
- return;
1636
- }
1637
- // If manual validation is present, skip individual reporting but track for aggregate
1638
- if (hasManualCheck) {
1639
- instances.push({ lineNumber: index + 1, lineContent: line.trim() });
1640
- return;
1641
- }
1642
- if (isTestFile) {
1643
- return; // Don't report in test files
1644
- }
1645
- instances.push({ lineNumber: index + 1, lineContent: line.trim() });
1646
- });
1647
- // Don't report if no instances found
1648
- if (instances.length === 0)
1649
- return;
1650
- // If manual validation exists, create a single info-level note
1651
- if (hasManualCheck && instances.length > 0) {
1652
- vulnerabilities.push({
1653
- id: `request-json-manual-${filePath}`,
1654
- filePath,
1655
- lineNumber: instances[0].lineNumber,
1656
- lineContent: instances[0].lineContent,
1657
- severity: 'info',
1658
- category: 'dangerous_function',
1659
- title: 'Request body with manual validation',
1660
- description: `API endpoint parses request body with manual validation patterns detected. Consider using a schema library (zod, yup) for more robust type-safe validation.`,
1661
- suggestedFix: 'While manual validation works, schema libraries provide better TypeScript integration and error messages.',
1662
- confidence: 'low',
1663
- layer: 2,
1664
- });
1665
- return;
1666
- }
1667
- // Aggregate if multiple instances without validation
1668
- if (instances.length >= 2) {
1669
- const lineNumbers = instances.map(i => i.lineNumber).slice(0, 5);
1670
- vulnerabilities.push({
1671
- id: `request-json-aggregated-${filePath}`,
1672
- filePath,
1673
- lineNumber: instances[0].lineNumber,
1674
- lineContent: `${instances.length} instances`,
1675
- severity: 'info',
1676
- category: 'dangerous_function',
1677
- title: `Request body without schema validation (${instances.length} instances)`,
1678
- description: `API endpoint parses request body without visible schema validation at lines: ${lineNumbers.join(', ')}. Consider validating the shape of incoming data.`,
1679
- suggestedFix: 'Add schema validation (e.g., zod): const body = await request.json(); const data = schema.parse(body);',
1680
- confidence: 'low',
1681
- layer: 2,
1682
- });
1683
- }
1684
- else {
1685
- // Single instance
1686
- vulnerabilities.push({
1687
- id: `request-json-${filePath}-${instances[0].lineNumber}`,
1688
- filePath,
1689
- lineNumber: instances[0].lineNumber,
1690
- lineContent: instances[0].lineContent,
1691
- severity: 'info',
1692
- category: 'dangerous_function',
1693
- title: 'Request body without schema validation',
1694
- description: 'API endpoint parses request body without visible schema validation. Consider validating the shape of incoming data.',
1695
- suggestedFix: 'Add schema validation (e.g., zod): const body = await request.json(); const data = schema.parse(body);',
1696
- confidence: 'low',
1697
- layer: 2,
1698
- });
1699
- }
1700
- }
1701
- //# sourceMappingURL=dangerous-functions.js.map