@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
@@ -0,0 +1,949 @@
1
+ /**
2
+ * Layer 2: Dangerous Function Call Analysis
3
+ *
4
+ * Detects usage of dangerous functions that can lead to security vulnerabilities.
5
+ * This module orchestrates detection across multiple specialized modules.
6
+ */
7
+
8
+ import type { Vulnerability, VulnerabilitySeverity } from '../../types'
9
+ import {
10
+ isComment,
11
+ isTestOrMockFile,
12
+ isScannerOrFixtureFile,
13
+ isSeedOrDataGenFile,
14
+ } from '../../utils/context-helpers'
15
+
16
+ // Pattern definitions
17
+ import {
18
+ DANGEROUS_FUNCTIONS,
19
+ matchesLanguage,
20
+ type DangerousFunctionPattern,
21
+ } from './patterns'
22
+
23
+ // Child process detection
24
+ import { isChildProcessExec, isChildProcessSpawn } from './child-process'
25
+
26
+ // DOM/XSS detection
27
+ import {
28
+ isStyleElementInnerHTML,
29
+ isStaticHTMLContent,
30
+ hasDOMPurifySanitization,
31
+ isLLMPromptContext,
32
+ isStaticBootstrapScript,
33
+ } from './dom-xss'
34
+
35
+ // JSON.parse detection
36
+ import { detectJSONParseSafe } from './json-parse'
37
+
38
+ // Math.random detection
39
+ import {
40
+ isCosmeticMathRandom,
41
+ classifyFunctionIntent,
42
+ analyzeToStringPattern,
43
+ extractMathRandomVariableName,
44
+ classifyVariableNameRisk,
45
+ analyzeMathRandomContext,
46
+ shouldSkipMathRandom,
47
+ } from './math-random'
48
+
49
+ // Request validation detection
50
+ import { detectRequestJsonValidation } from './request-validation'
51
+
52
+ // Utilities
53
+ import { extractFunctionContext } from './utils/control-flow'
54
+ import { hasSQLWhitelistValidation } from './utils/schema-validation'
55
+ import { hasOnlyStaticInputs, hasPathTraversalProtection } from './utils/helpers'
56
+
57
+ // Re-export types and patterns for external use
58
+ export { DANGEROUS_FUNCTIONS, type DangerousFunctionPattern } from './patterns'
59
+
60
+ /**
61
+ * Main detection function for dangerous function calls
62
+ */
63
+ export function detectDangerousFunctions(
64
+ content: string,
65
+ filePath: string
66
+ ): Vulnerability[] {
67
+ const vulnerabilities: Vulnerability[] = []
68
+
69
+ // Skip scanner/fixture files to avoid self-detection
70
+ if (isScannerOrFixtureFile(filePath)) {
71
+ return vulnerabilities
72
+ }
73
+
74
+ const lines = content.split('\n')
75
+ const isTestFile = isTestOrMockFile(filePath)
76
+
77
+ lines.forEach((line, index) => {
78
+ // Skip comment lines
79
+ if (isComment(line)) return
80
+
81
+ for (const funcPattern of DANGEROUS_FUNCTIONS) {
82
+ // Check language filter
83
+ if (!matchesLanguage(filePath, funcPattern.languages)) continue
84
+
85
+ const regex = new RegExp(
86
+ funcPattern.pattern.source,
87
+ funcPattern.pattern.flags
88
+ )
89
+
90
+ if (regex.test(line)) {
91
+ // Special handling for innerHTML patterns
92
+ if (
93
+ funcPattern.name === 'innerHTML assignment' ||
94
+ funcPattern.name === 'dangerouslySetInnerHTML'
95
+ ) {
96
+ handleInnerHTMLPattern(
97
+ funcPattern,
98
+ line,
99
+ content,
100
+ index,
101
+ filePath,
102
+ isTestFile,
103
+ vulnerabilities
104
+ )
105
+ break
106
+ }
107
+
108
+ // Note: JSON.parse is now handled by standalone detectJSONParseSafe() function
109
+ // which provides better source-aware severity classification
110
+
111
+ // Special handling for eval and Function constructor
112
+ if (
113
+ funcPattern.name === 'eval() usage' ||
114
+ funcPattern.name === 'Function constructor'
115
+ ) {
116
+ if (
117
+ handleEvalPattern(
118
+ funcPattern,
119
+ line,
120
+ content,
121
+ index,
122
+ filePath,
123
+ isTestFile,
124
+ vulnerabilities
125
+ )
126
+ ) {
127
+ break
128
+ }
129
+ continue
130
+ }
131
+
132
+ // Special handling for child_process exec - verify it's not RegExp.exec
133
+ if (funcPattern.name === 'child_process exec') {
134
+ if (
135
+ handleChildProcessPattern(
136
+ funcPattern,
137
+ line,
138
+ content,
139
+ index,
140
+ filePath,
141
+ isTestFile,
142
+ vulnerabilities
143
+ )
144
+ ) {
145
+ break
146
+ }
147
+ continue
148
+ }
149
+
150
+ // Special handling for SQL patterns - check for whitelist validation
151
+ if (
152
+ funcPattern.name === 'Raw SQL query construction' ||
153
+ funcPattern.name === 'SQL template literal'
154
+ ) {
155
+ handleSQLPattern(
156
+ funcPattern,
157
+ line,
158
+ content,
159
+ index,
160
+ filePath,
161
+ isTestFile,
162
+ vulnerabilities
163
+ )
164
+ break
165
+ }
166
+
167
+ // Special handling for dynamic file paths - check for path traversal protection
168
+ if (
169
+ funcPattern.name === 'Dynamic file path' ||
170
+ funcPattern.name === 'Path traversal risk'
171
+ ) {
172
+ handleFilePathPattern(
173
+ funcPattern,
174
+ line,
175
+ content,
176
+ index,
177
+ filePath,
178
+ isTestFile,
179
+ vulnerabilities
180
+ )
181
+ break
182
+ }
183
+
184
+ // Special handling for Math.random
185
+ if (funcPattern.name === 'Math.random for security') {
186
+ handleMathRandomPattern(
187
+ funcPattern,
188
+ line,
189
+ content,
190
+ index,
191
+ filePath,
192
+ isTestFile,
193
+ vulnerabilities
194
+ )
195
+ break
196
+ }
197
+
198
+ // Special handling for Python subprocess/os.system
199
+ if (funcPattern.name === 'os.system/subprocess (Python)') {
200
+ handlePythonSubprocessPattern(
201
+ funcPattern,
202
+ line,
203
+ content,
204
+ index,
205
+ filePath,
206
+ isTestFile,
207
+ vulnerabilities
208
+ )
209
+ break
210
+ }
211
+
212
+ // Standard handling for all other patterns
213
+ handleStandardPattern(
214
+ funcPattern,
215
+ line,
216
+ index,
217
+ filePath,
218
+ isTestFile,
219
+ vulnerabilities
220
+ )
221
+ break // Only report once per line
222
+ }
223
+ }
224
+ })
225
+
226
+ // Additional standalone checks (not in DANGEROUS_FUNCTIONS array)
227
+
228
+ // JSON.parse source-aware detection
229
+ detectJSONParseSafe(content, filePath, isTestFile, vulnerabilities)
230
+
231
+ // request.json() / req.json() schema validation suggestion
232
+ detectRequestJsonValidation(content, filePath, isTestFile, vulnerabilities)
233
+
234
+ return vulnerabilities
235
+ }
236
+
237
+ /**
238
+ * Handle innerHTML/dangerouslySetInnerHTML patterns
239
+ */
240
+ function handleInnerHTMLPattern(
241
+ funcPattern: DangerousFunctionPattern,
242
+ line: string,
243
+ content: string,
244
+ index: number,
245
+ filePath: string,
246
+ isTestFile: boolean,
247
+ vulnerabilities: Vulnerability[]
248
+ ): void {
249
+ // Check if this is a style element (CSS injection is not XSS)
250
+ if (isStyleElementInnerHTML(line, content, index)) {
251
+ // Style elements with CSS are safe - don't report anything
252
+ // CSS cannot execute JavaScript, so there's no XSS risk
253
+ return
254
+ }
255
+
256
+ // Check if this uses static content only - skip entirely (safe)
257
+ if (isStaticHTMLContent(line, content, index)) {
258
+ return // Static HTML is safe - no finding needed
259
+ }
260
+
261
+ // Check if DOMPurify or similar sanitization is used - skip entirely (safe)
262
+ if (hasDOMPurifySanitization(line, content, index)) {
263
+ return // Sanitized HTML is safe - no finding needed
264
+ }
265
+
266
+ // Check if this is a static bootstrap script (e.g., theme/font loader) - skip entirely (safe)
267
+ if (isStaticBootstrapScript(line, content, index)) {
268
+ return // Static bootstrap scripts are safe - no finding needed
269
+ }
270
+
271
+ // Check if this is in LLM prompt context (not XSS - it's prompt injection)
272
+ if (isLLMPromptContext(line, content, filePath)) {
273
+ vulnerabilities.push({
274
+ id: `dangerous-func-${filePath}-${index + 1}-prompt-injection`,
275
+ filePath,
276
+ lineNumber: index + 1,
277
+ lineContent: line.trim(),
278
+ severity: 'info',
279
+ category: 'ai_pattern',
280
+ title: 'Potential prompt injection risk',
281
+ description:
282
+ '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.',
283
+ suggestedFix:
284
+ 'Consider input validation, content filtering, or structured prompts to limit prompt injection risk.',
285
+ confidence: 'low',
286
+ layer: 2,
287
+ })
288
+ return
289
+ }
290
+
291
+ // Dynamic content - full severity, needs AI validation
292
+ let severity = funcPattern.severity
293
+ if (isTestFile) {
294
+ severity = 'low'
295
+ }
296
+
297
+ vulnerabilities.push({
298
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
299
+ filePath,
300
+ lineNumber: index + 1,
301
+ lineContent: line.trim(),
302
+ severity,
303
+ category: 'dangerous_function',
304
+ title: funcPattern.name,
305
+ description:
306
+ funcPattern.description +
307
+ ' This appears to use dynamic content which increases XSS risk.' +
308
+ (isTestFile ? ' (in test file)' : ''),
309
+ suggestedFix: funcPattern.suggestedFix,
310
+ confidence: isTestFile ? 'low' : 'high',
311
+ layer: 2,
312
+ requiresAIValidation: true, // Dynamic HTML needs validation
313
+ })
314
+ }
315
+
316
+ /**
317
+ * Handle eval and Function constructor patterns
318
+ * Returns true if a finding was added, false otherwise
319
+ */
320
+ function handleEvalPattern(
321
+ funcPattern: DangerousFunctionPattern,
322
+ line: string,
323
+ content: string,
324
+ index: number,
325
+ filePath: string,
326
+ isTestFile: boolean,
327
+ vulnerabilities: Vulnerability[]
328
+ ): boolean {
329
+ // Check if "eval" or "Function" appears inside a string literal
330
+ // e.g., const docs = "Don't use eval() in production"
331
+ // This is NOT an actual eval call, just documentation/comments
332
+ const evalInsideStringPattern = /(['"`])(?:[^\\]|\\.)*?\beval\s*\(.*?\1/
333
+ const functionInsideStringPattern = /(['"`])(?:[^\\]|\\.)*?\bFunction\s*\(.*?\1/
334
+ if (evalInsideStringPattern.test(line) || functionInsideStringPattern.test(line)) {
335
+ return true // Skip - this is just a string mentioning eval, not actual eval()
336
+ }
337
+
338
+ // Suppress entirely in test files - test files legitimately test eval behavior
339
+ if (isTestFile) {
340
+ return true // Skip reporting entirely
341
+ }
342
+
343
+ // Check if eval is inside a test assertion (expect(), test(), it(), describe())
344
+ const testAssertionPattern = /\b(expect|test|it|describe)\s*\(/
345
+ if (testAssertionPattern.test(line)) {
346
+ return true // Skip reporting - this is testing eval behavior
347
+ }
348
+
349
+ // Check if inputs are static literals (low risk) - skip entirely
350
+ if (hasOnlyStaticInputs(line, content, index)) {
351
+ return true // Static eval is safe enough - no finding needed
352
+ }
353
+
354
+ vulnerabilities.push({
355
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
356
+ filePath,
357
+ lineNumber: index + 1,
358
+ lineContent: line.trim(),
359
+ severity: funcPattern.severity,
360
+ category: 'dangerous_function',
361
+ title: funcPattern.name,
362
+ description: funcPattern.description,
363
+ suggestedFix: funcPattern.suggestedFix,
364
+ confidence: 'high',
365
+ layer: 2,
366
+ requiresAIValidation: true, // Code execution patterns need validation
367
+ })
368
+ return true
369
+ }
370
+
371
+ /**
372
+ * Handle child_process exec patterns
373
+ * Returns true if a finding was added, false otherwise
374
+ */
375
+ function handleChildProcessPattern(
376
+ funcPattern: DangerousFunctionPattern,
377
+ line: string,
378
+ content: string,
379
+ index: number,
380
+ filePath: string,
381
+ isTestFile: boolean,
382
+ vulnerabilities: Vulnerability[]
383
+ ): boolean {
384
+ // First check if this is actually from child_process (not RegExp.exec)
385
+ const isExecMatch = /\bexec\s*\(/.test(line)
386
+ const isOtherMatch = /\b(execSync|spawn|spawnSync|execFile)\s*\(/.test(line)
387
+
388
+ if (isExecMatch && !isOtherMatch) {
389
+ // This matched 'exec(' - verify it's from child_process
390
+ if (!isChildProcessExec(content, line)) {
391
+ // This is RegExp.exec or similar - skip
392
+ return false
393
+ }
394
+ } else if (isOtherMatch) {
395
+ // This matched spawn/execSync/etc - verify child_process import
396
+ if (!isChildProcessSpawn(content, line)) {
397
+ // No child_process import - skip
398
+ return false
399
+ }
400
+ }
401
+
402
+ // Check if arguments are validated via allowlist
403
+ const lines = content.split('\n')
404
+ const contextStart = Math.max(0, index - 15)
405
+ const contextEnd = Math.min(lines.length, index + 5)
406
+ const context = lines.slice(contextStart, contextEnd).join('\n')
407
+
408
+ // Detect allowlist validation patterns before exec/spawn
409
+ const hasArgAllowlist =
410
+ /allowedArgs\.includes\s*\(/i.test(context) ||
411
+ /if\s*\(\s*!?allowedArgs\.includes/i.test(context) ||
412
+ /if\s*\(\s*!?\w+Args\.includes/i.test(context) ||
413
+ /validArgs\.includes/i.test(context) ||
414
+ // ALLOWED_COMMANDS pattern (common naming convention)
415
+ /ALLOWED_\w+\.includes\s*\(/i.test(context) ||
416
+ /if\s*\(\s*!?ALLOWED_\w+\.includes/i.test(context) ||
417
+ // allowedCommands, validCommands, safeCommands
418
+ /allowed(?:Commands?|Cmds?)\.includes\s*\(/i.test(context) ||
419
+ /valid(?:Commands?|Cmds?)\.includes\s*\(/i.test(context) ||
420
+ /safe(?:Commands?|Cmds?)\.includes\s*\(/i.test(context) ||
421
+ // Generic whitelist/allowlist check
422
+ /(?:whitelist|allowlist)\.includes\s*\(/i.test(context)
423
+
424
+ // execFile with hardcoded command is safe (safer than exec)
425
+ const isExecFileWithHardcodedCmd = /execFile\s*\(\s*['"][^'"]+['"]/i.test(line)
426
+
427
+ if (hasArgAllowlist || isExecFileWithHardcodedCmd) {
428
+ return true // Allowlisted or execFile with hardcoded command - safe
429
+ }
430
+
431
+ if (hasOnlyStaticInputs(line, content, index)) {
432
+ return true // Static command is safe - no finding needed
433
+ }
434
+
435
+ // Dynamic command - report with standard severity
436
+ let severity = funcPattern.severity
437
+ let confidence: 'high' | 'medium' | 'low' = 'high'
438
+
439
+ if (isTestFile) {
440
+ if (severity === 'critical') {
441
+ severity = 'medium'
442
+ } else if (severity === 'high') {
443
+ severity = 'low'
444
+ } else {
445
+ severity = 'info'
446
+ }
447
+ confidence = 'low'
448
+ }
449
+
450
+ vulnerabilities.push({
451
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
452
+ filePath,
453
+ lineNumber: index + 1,
454
+ lineContent: line.trim(),
455
+ severity,
456
+ category: 'dangerous_function',
457
+ title: funcPattern.name,
458
+ description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
459
+ suggestedFix: funcPattern.suggestedFix,
460
+ confidence,
461
+ layer: 2,
462
+ })
463
+ return true
464
+ }
465
+
466
+ /**
467
+ * Handle SQL injection patterns
468
+ */
469
+ function handleSQLPattern(
470
+ funcPattern: DangerousFunctionPattern,
471
+ line: string,
472
+ content: string,
473
+ index: number,
474
+ filePath: string,
475
+ isTestFile: boolean,
476
+ vulnerabilities: Vulnerability[]
477
+ ): void {
478
+ // Check for whitelist validation - skip entirely (safe)
479
+ if (hasSQLWhitelistValidation(content, index)) {
480
+ return // Whitelist validated - safe, no finding needed
481
+ }
482
+
483
+ // Check for ORM methods (not raw SQL) - skip entirely (safe)
484
+ // Prisma: prisma.user.findMany({ where: {...} })
485
+ // Sequelize: Model.findAll({ where: {...} })
486
+ // TypeORM: repository.find({ where: {...} })
487
+ const ormMethodPattern = /\.(findMany|findUnique|findFirst|findAll|find|create|update|delete|upsert)\s*\(\s*\{/i
488
+ if (ormMethodPattern.test(line)) {
489
+ return // ORM method - safe, no finding needed
490
+ }
491
+
492
+ // Check for parameterized queries - skip entirely (safe)
493
+ // e.g., db.query('SELECT * FROM users WHERE id = $1', [userId])
494
+ const parameterizedQueryPattern = /\.\s*(query|execute)\s*\(\s*['"`][^${}]+['"`]\s*,\s*\[/
495
+ if (parameterizedQueryPattern.test(line)) {
496
+ return // Parameterized query - safe, no finding needed
497
+ }
498
+
499
+ // Check for Prisma tagged template literal - these ARE parameterized (safe)
500
+ // Prisma's $queryRaw`...${var}...` treats ${} as parameterized values, not string interpolation
501
+ // e.g., prisma.$queryRaw`SELECT * FROM users WHERE id = ${userId}`
502
+ const prismaTaggedTemplatePattern = /\$queryRaw\s*`[^`]*\$\{/i
503
+ if (prismaTaggedTemplatePattern.test(line)) {
504
+ return // Prisma tagged template - parameterized and safe, no finding needed
505
+ }
506
+
507
+ // Check for schema-validated input (zod .enum() for table/column names)
508
+ // e.g., z.enum(['users', 'posts']).parse(input) followed by SQL
509
+ const lines = content.split('\n')
510
+ const contextStart = Math.max(0, index - 20)
511
+ const contextEnd = index
512
+ const previousContext = lines.slice(contextStart, contextEnd).join('\n')
513
+
514
+ // Detect zod enum validation for SQL identifiers
515
+ const hasSchemaValidation =
516
+ /z\s*\.\s*enum\s*\(\s*\[['"][^'"]+['"]/i.test(previousContext) ||
517
+ /\.parse\s*\(\s*JSON\.parse/.test(previousContext) ||
518
+ // Allow validated table/column names from parsed schema
519
+ /schema\.parse/.test(previousContext) ||
520
+ /const\s+parsed\s*=\s*schema/.test(previousContext)
521
+
522
+ if (hasSchemaValidation) {
523
+ return // Schema-validated SQL identifiers - safe, no finding needed
524
+ }
525
+
526
+ // No whitelist - report with standard severity
527
+ let severity = funcPattern.severity
528
+ let confidence: 'high' | 'medium' | 'low' = 'high'
529
+
530
+ if (isTestFile) {
531
+ if (severity === 'critical') {
532
+ severity = 'medium'
533
+ } else if (severity === 'high') {
534
+ severity = 'low'
535
+ } else {
536
+ severity = 'info'
537
+ }
538
+ confidence = 'low'
539
+ }
540
+
541
+ vulnerabilities.push({
542
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
543
+ filePath,
544
+ lineNumber: index + 1,
545
+ lineContent: line.trim(),
546
+ severity,
547
+ category: 'dangerous_function',
548
+ title: funcPattern.name,
549
+ description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
550
+ suggestedFix: funcPattern.suggestedFix,
551
+ confidence,
552
+ layer: 2,
553
+ })
554
+ }
555
+
556
+ /**
557
+ * Handle dynamic file path patterns
558
+ */
559
+ function handleFilePathPattern(
560
+ funcPattern: DangerousFunctionPattern,
561
+ line: string,
562
+ content: string,
563
+ index: number,
564
+ filePath: string,
565
+ isTestFile: boolean,
566
+ vulnerabilities: Vulnerability[]
567
+ ): void {
568
+ // Check file context for CLI/tooling (lower risk)
569
+ const isCLITool =
570
+ /\/(cli|scripts?|tools?|bin)\//i.test(filePath) ||
571
+ /cli\.(ts|js)$/i.test(filePath)
572
+
573
+ // Check for GitHub Action context (workflow-controlled paths)
574
+ const isGitHubAction =
575
+ /\/(github-action|actions?)\//i.test(filePath) ||
576
+ /action\.(ts|js)$/i.test(filePath)
577
+
578
+ // Check for utility/helper file context (called by trusted code)
579
+ const isUtilityFile =
580
+ /\/(utils?|helpers?|lib|common|shared)\//i.test(filePath) ||
581
+ /(util(s)?|helper(s)?|checksum|hash)\.(ts|js)$/i.test(filePath)
582
+
583
+ // Get surrounding context for protection check
584
+ const lines = content.split('\n')
585
+ const contextStart = Math.max(0, index - 10)
586
+ const contextEnd = Math.min(lines.length, index + 10)
587
+ const context = lines.slice(contextStart, contextEnd).join('\n')
588
+
589
+ // Check if path comes from directory iteration (fs.readdir, fs.readdirSync)
590
+ // These paths are filesystem-controlled, not user input
591
+ const hasDirectoryIteration =
592
+ /\b(readdir|readdirSync|opendir|opendirSync)\s*\(/.test(content) &&
593
+ (/for\s*\(\s*(const|let|var)\s+\w+\s+of/.test(context) ||
594
+ /\.forEach\s*\(/.test(context) ||
595
+ /entry\.(name|isFile|isDirectory)/.test(context) ||
596
+ /dirent\.(name|isFile|isDirectory)/.test(context))
597
+
598
+ if (hasPathTraversalProtection(context, line)) {
599
+ vulnerabilities.push({
600
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
601
+ filePath,
602
+ lineNumber: index + 1,
603
+ lineContent: line.trim(),
604
+ severity: 'info',
605
+ category: 'dangerous_function',
606
+ title: funcPattern.name + ' (protected)',
607
+ description:
608
+ 'Dynamic file path with path traversal protection detected. Verify the protection is complete and covers all attack vectors.',
609
+ suggestedFix:
610
+ 'Ensure path normalization and base directory checks are applied consistently.',
611
+ confidence: 'low',
612
+ layer: 2,
613
+ })
614
+ return
615
+ }
616
+
617
+ // Directory iteration paths are filesystem-controlled (not user input)
618
+ if (hasDirectoryIteration) {
619
+ // Skip entirely - paths from fs.readdir are not user-controlled
620
+ return
621
+ }
622
+
623
+ // GitHub Action paths are workflow-controlled (not arbitrary user input)
624
+ if (isGitHubAction) {
625
+ vulnerabilities.push({
626
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
627
+ filePath,
628
+ lineNumber: index + 1,
629
+ lineContent: line.trim(),
630
+ severity: 'info',
631
+ category: 'dangerous_function',
632
+ title: funcPattern.name + ' (GitHub Action)',
633
+ description:
634
+ 'Dynamic file path in GitHub Action. Paths are typically controlled by workflow configuration, not arbitrary user input.',
635
+ suggestedFix:
636
+ 'Verify paths come from trusted action inputs or environment variables.',
637
+ confidence: 'low',
638
+ layer: 2,
639
+ })
640
+ return
641
+ }
642
+
643
+ // CLI tools with dynamic paths are lower risk (trusted operator)
644
+ if (isCLITool) {
645
+ vulnerabilities.push({
646
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
647
+ filePath,
648
+ lineNumber: index + 1,
649
+ lineContent: line.trim(),
650
+ severity: 'info',
651
+ category: 'dangerous_function',
652
+ title: funcPattern.name + ' (CLI tool)',
653
+ description:
654
+ 'Dynamic file path in CLI tool. CLI tools typically have trusted operators, but consider adding path validation if user input is involved.',
655
+ suggestedFix:
656
+ 'Add path validation if accepting paths from untrusted sources.',
657
+ confidence: 'low',
658
+ layer: 2,
659
+ })
660
+ return
661
+ }
662
+
663
+ // Utility/helper files with function parameters are lower risk (called by trusted code)
664
+ // Check if path variable appears to be a function parameter, not from request
665
+ const hasRequestData = /req\.(params|query|body)|request\.(params|query|body)/i.test(context)
666
+ if (isUtilityFile && !hasRequestData) {
667
+ // Skip entirely - utility functions receive paths from trusted callers
668
+ return
669
+ }
670
+
671
+ // Standard handling for unprotected paths
672
+ let severity = funcPattern.severity
673
+ let confidence: 'high' | 'medium' | 'low' = 'high'
674
+
675
+ if (isTestFile) {
676
+ if (severity === 'critical') {
677
+ severity = 'medium'
678
+ } else if (severity === 'high') {
679
+ severity = 'low'
680
+ } else {
681
+ severity = 'info'
682
+ }
683
+ confidence = 'low'
684
+ }
685
+
686
+ vulnerabilities.push({
687
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
688
+ filePath,
689
+ lineNumber: index + 1,
690
+ lineContent: line.trim(),
691
+ severity,
692
+ category: 'dangerous_function',
693
+ title: funcPattern.name,
694
+ description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
695
+ suggestedFix: funcPattern.suggestedFix,
696
+ confidence,
697
+ layer: 2,
698
+ })
699
+ }
700
+
701
+ /**
702
+ * Handle Math.random patterns with context-aware severity
703
+ */
704
+ function handleMathRandomPattern(
705
+ funcPattern: DangerousFunctionPattern,
706
+ line: string,
707
+ content: string,
708
+ index: number,
709
+ filePath: string,
710
+ isTestFile: boolean,
711
+ vulnerabilities: Vulnerability[]
712
+ ): void {
713
+ // Skip entirely for certain contexts
714
+ if (shouldSkipMathRandom(content, filePath, index)) {
715
+ return
716
+ }
717
+
718
+ // Analyze context
719
+ const functionName = extractFunctionContext(content, index)
720
+ const functionIntent = classifyFunctionIntent(functionName)
721
+ const toStringPattern = analyzeToStringPattern(line)
722
+ const variableName = extractMathRandomVariableName(line)
723
+ const variableRisk = classifyVariableNameRisk(variableName)
724
+ const context = analyzeMathRandomContext(content, filePath, index)
725
+
726
+ // Determine severity based on all factors
727
+ let severity: VulnerabilitySeverity
728
+ let confidence: 'high' | 'medium' | 'low'
729
+ let description: string
730
+ let suggestedFix: string
731
+ let explanation = ''
732
+
733
+ // Variable name indicates security risk - check this FIRST before toString patterns
734
+ // This ensures 'secret', 'token', 'key' etc. are always flagged as high
735
+ if (variableRisk === 'high') {
736
+ severity = 'high'
737
+ confidence = 'high'
738
+ // Update context description to indicate security context
739
+ context.contextDescription = 'security-sensitive variable'
740
+ description = `Math.random() assigned to security-sensitive variable '${variableName}'. Math.random() is NOT cryptographically secure.`
741
+ suggestedFix =
742
+ 'Use crypto.randomBytes() or crypto.getRandomValues() for security-sensitive values.'
743
+ }
744
+ // Security-sensitive contexts get high severity
745
+ else if (context.inSecurityContext || functionIntent === 'security') {
746
+ severity = 'high'
747
+ confidence = 'high'
748
+ description =
749
+ 'Math.random() is being used in a security-sensitive context. This is NOT cryptographically secure and should be replaced.'
750
+ suggestedFix =
751
+ 'Use crypto.randomBytes() for Node.js or crypto.getRandomValues() for browsers.'
752
+ }
753
+ // Test contexts get info severity
754
+ else if (context.inTestContext) {
755
+ severity = 'info'
756
+ confidence = 'low'
757
+ description =
758
+ 'Math.random() in test context. Acceptable for test data generation.'
759
+ suggestedFix = 'No change needed for test data.'
760
+ }
761
+ // UUID/CAPTCHA generation - legitimate use
762
+ else if (functionIntent === 'uuid' || functionIntent === 'captcha') {
763
+ severity = 'info'
764
+ confidence = 'low'
765
+ description = `Math.random() used for ${functionIntent === 'uuid' ? 'ID generation' : 'CAPTCHA/puzzle'} (not security-sensitive).`
766
+ suggestedFix =
767
+ 'For truly unique IDs, consider crypto.randomUUID(). For security tokens, use crypto.randomBytes().'
768
+ }
769
+ // Demo/seed data - legitimate use
770
+ else if (functionIntent === 'demo') {
771
+ severity = 'info'
772
+ confidence = 'low'
773
+ description =
774
+ 'Math.random() for demo/seed data generation. Acceptable for non-production data.'
775
+ suggestedFix = 'No change needed for demo/seed data.'
776
+ }
777
+ // Short UI IDs (.toString(36).substring(2,9)) - info
778
+ else if (toStringPattern.intent === 'short-ui-id') {
779
+ severity = 'info'
780
+ confidence = 'low'
781
+ explanation = ` (${toStringPattern.truncationLength || '?'}-char string)`
782
+ // Override context description for UI IDs
783
+ context.contextDescription = 'UI identifier generation'
784
+ description = `Math.random() generating short UI identifier${explanation}. Acceptable for React keys, temp IDs.`
785
+ suggestedFix =
786
+ 'For security tokens, use crypto.randomBytes(). For unique IDs, crypto.randomUUID().'
787
+ }
788
+ // Business IDs (.toString(36) with medium truncation) - low
789
+ else if (toStringPattern.intent === 'business-id') {
790
+ severity = 'low'
791
+ confidence = 'low'
792
+ explanation = variableName ? ` (variable: ${variableName})` : ''
793
+ description = `Math.random() generating business identifier${explanation}. Verify this is not used for security purposes.`
794
+ suggestedFix =
795
+ 'For business IDs, crypto.randomUUID() is preferred. For security tokens, use crypto.randomBytes().'
796
+ }
797
+ // Full token (.toString(36) without truncation) - severity based on variable name
798
+ else if (toStringPattern.intent === 'full-token') {
799
+ // Note: high-risk variable names are already handled above
800
+ if (variableRisk === 'low') {
801
+ severity = 'low'
802
+ confidence = 'low'
803
+ } else {
804
+ severity = 'medium'
805
+ confidence = 'medium'
806
+ }
807
+ explanation = variableName ? ` (variable: ${variableName})` : ''
808
+ description = `Math.random() generating full-length random string${explanation}. This pattern is often used for security tokens.`
809
+ suggestedFix =
810
+ 'Use crypto.randomBytes() for security tokens. Use crypto.randomUUID() for unique IDs.'
811
+ }
812
+ // Business logic context - low
813
+ else if (context.inBusinessLogicContext) {
814
+ severity = 'low'
815
+ confidence = 'low'
816
+ description =
817
+ 'Math.random() in business logic context (backoff, sampling, experiments). Verify this is not for security.'
818
+ suggestedFix =
819
+ 'If used for security, replace with crypto.randomBytes(). Otherwise, usage is acceptable.'
820
+ }
821
+ // Unknown context - medium
822
+ else {
823
+ severity = 'medium'
824
+ confidence = 'medium'
825
+ description =
826
+ 'Math.random() is being used. Verify this is not for security-critical purposes like tokens, session IDs, or cryptographic operations.'
827
+ suggestedFix =
828
+ 'If used for security, replace with crypto.randomBytes(). For unique IDs, use crypto.randomUUID()'
829
+ }
830
+
831
+ // Update title with context
832
+ const title = `Math.random() in ${context.contextDescription}${explanation}`
833
+
834
+ vulnerabilities.push({
835
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
836
+ filePath,
837
+ lineNumber: index + 1,
838
+ lineContent: line.trim(),
839
+ severity,
840
+ category: 'dangerous_function',
841
+ title,
842
+ description,
843
+ suggestedFix,
844
+ confidence,
845
+ layer: 2,
846
+ })
847
+ }
848
+
849
+ /**
850
+ * Handle Python subprocess/os.system patterns
851
+ * Safe pattern: subprocess.run(['static', 'args']) without shell=True
852
+ * Unsafe pattern: subprocess.run(command, shell=True) or os.system(command)
853
+ */
854
+ function handlePythonSubprocessPattern(
855
+ funcPattern: DangerousFunctionPattern,
856
+ line: string,
857
+ content: string,
858
+ index: number,
859
+ filePath: string,
860
+ isTestFile: boolean,
861
+ vulnerabilities: Vulnerability[]
862
+ ): void {
863
+ // os.system is always dangerous - no safe usage
864
+ if (/os\.system\s*\(/i.test(line)) {
865
+ handleStandardPattern(funcPattern, line, index, filePath, isTestFile, vulnerabilities)
866
+ return
867
+ }
868
+
869
+ // Check for subprocess with list args and no shell=True (safe)
870
+ // Pattern: subprocess.run(['git', 'status', '--porcelain'], ...)
871
+ // This is safe because args are not parsed by shell
872
+ const hasListArgs = /subprocess\.(run|call|check_output|Popen)\s*\(\s*\[/i.test(line)
873
+
874
+ // Check for shell=True (dangerous)
875
+ // Look at next line too in case it's multi-line
876
+ const lines = content.split('\n')
877
+ const nextLine = lines[index + 1] || ''
878
+ const combinedLines = line + ' ' + nextLine
879
+ const hasShellTrue = /shell\s*=\s*True/i.test(combinedLines)
880
+
881
+ // Safe: list args without shell=True
882
+ if (hasListArgs && !hasShellTrue) {
883
+ // Check if the command is completely static (all string literals in the list)
884
+ const staticListPattern = /subprocess\.(run|call|check_output|Popen)\s*\(\s*\[\s*(['"][^'"]*['"],?\s*)+\]/i
885
+ if (staticListPattern.test(line)) {
886
+ // Completely static command list - safe, skip entirely
887
+ return
888
+ }
889
+ // List args but might have variable args - lower severity
890
+ vulnerabilities.push({
891
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
892
+ filePath,
893
+ lineNumber: index + 1,
894
+ lineContent: line.trim(),
895
+ severity: 'low',
896
+ category: 'dangerous_function',
897
+ title: funcPattern.name + ' (list args)',
898
+ description:
899
+ 'subprocess with list arguments (safer than shell=True). Verify command arguments are validated.',
900
+ suggestedFix: 'Ensure all arguments are validated and sanitized.',
901
+ confidence: 'low',
902
+ layer: 2,
903
+ })
904
+ return
905
+ }
906
+
907
+ // Standard handling for shell=True or non-list args
908
+ handleStandardPattern(funcPattern, line, index, filePath, isTestFile, vulnerabilities)
909
+ }
910
+
911
+ /**
912
+ * Handle standard patterns without special logic
913
+ */
914
+ function handleStandardPattern(
915
+ funcPattern: DangerousFunctionPattern,
916
+ line: string,
917
+ index: number,
918
+ filePath: string,
919
+ isTestFile: boolean,
920
+ vulnerabilities: Vulnerability[]
921
+ ): void {
922
+ let severity = funcPattern.severity
923
+ let confidence: 'high' | 'medium' | 'low' = 'high'
924
+
925
+ if (isTestFile) {
926
+ if (severity === 'critical') {
927
+ severity = 'medium'
928
+ } else if (severity === 'high') {
929
+ severity = 'low'
930
+ } else {
931
+ severity = 'info'
932
+ }
933
+ confidence = 'low'
934
+ }
935
+
936
+ vulnerabilities.push({
937
+ id: `dangerous-func-${filePath}-${index + 1}-${funcPattern.name}`,
938
+ filePath,
939
+ lineNumber: index + 1,
940
+ lineContent: line.trim(),
941
+ severity,
942
+ category: 'dangerous_function',
943
+ title: funcPattern.name,
944
+ description: funcPattern.description + (isTestFile ? ' (in test file)' : ''),
945
+ suggestedFix: funcPattern.suggestedFix,
946
+ confidence,
947
+ layer: 2,
948
+ })
949
+ }