@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,218 @@
1
+ /**
2
+ * Tests for Rule Metadata Registry (PRO-82)
3
+ */
4
+
5
+ import { RULE_REGISTRY, getRuleMetadata, getAllCategories, hasMetadata, type RuleMetadata } from '../metadata'
6
+ import type { VulnerabilityCategory } from '../../types'
7
+
8
+ // All vulnerability categories that should have metadata
9
+ const ALL_CATEGORIES: VulnerabilityCategory[] = [
10
+ 'hardcoded_secret',
11
+ 'high_entropy_string',
12
+ 'sensitive_variable',
13
+ 'security_bypass',
14
+ 'dangerous_function',
15
+ 'sql_injection',
16
+ 'xss',
17
+ 'command_injection',
18
+ 'insecure_config',
19
+ 'missing_auth',
20
+ 'suspicious_package',
21
+ 'cors_misconfiguration',
22
+ 'root_container',
23
+ 'dangerous_file',
24
+ 'ai_pattern',
25
+ 'sensitive_url',
26
+ 'weak_crypto',
27
+ 'data_exposure',
28
+ 'ai_prompt_injection',
29
+ 'ai_unsafe_execution',
30
+ 'ai_overpermissive_tool',
31
+ 'ai_rag_exfiltration',
32
+ 'ai_endpoint_unprotected',
33
+ 'ai_schema_mismatch',
34
+ // AI Detection Roadmap Phase 1
35
+ 'ai_package_hallucination',
36
+ 'ai_rag_corpus_poisoning',
37
+ 'ai_rag_pii_leakage',
38
+ 'ai_mcp_tool_poisoning',
39
+ 'ai_mcp_credential_issue',
40
+ 'ai_mcp_confused_deputy',
41
+ // Phase 1 Enhancement Backlog
42
+ 'ai_mcp_description_injection',
43
+ 'ai_mcp_server_shadowing',
44
+ 'ai_mcp_config_secrets',
45
+ 'ai_mcp_config_permissions',
46
+ 'ai_rag_query_injection',
47
+ 'ai_rag_embedding_poisoning',
48
+ 'ai_rag_chunk_injection',
49
+ 'ai_package_typosquat',
50
+ 'ai_package_malicious',
51
+ // AI Detection Roadmap Phase 2
52
+ 'ai_unsafe_model_load',
53
+ 'ai_unverified_model',
54
+ 'ai_unsafe_finetuning',
55
+ 'ai_excessive_agency',
56
+ ]
57
+
58
+ describe('Rule Metadata Registry', () => {
59
+ describe('RULE_REGISTRY coverage', () => {
60
+ it('should have metadata for all vulnerability categories', () => {
61
+ for (const category of ALL_CATEGORIES) {
62
+ expect(RULE_REGISTRY[category]).toBeDefined()
63
+ expect(RULE_REGISTRY[category].name).toBeTruthy()
64
+ }
65
+ })
66
+
67
+ it('should have all required fields for each category', () => {
68
+ for (const category of ALL_CATEGORIES) {
69
+ const metadata = RULE_REGISTRY[category]
70
+
71
+ expect(metadata.name).toBeDefined()
72
+ expect(typeof metadata.name).toBe('string')
73
+ expect(metadata.name.length).toBeGreaterThan(0)
74
+
75
+ expect(metadata.whyItMatters).toBeDefined()
76
+ expect(typeof metadata.whyItMatters).toBe('string')
77
+ expect(metadata.whyItMatters.length).toBeGreaterThan(20)
78
+
79
+ expect(metadata.fixSteps).toBeDefined()
80
+ expect(Array.isArray(metadata.fixSteps)).toBe(true)
81
+ expect(metadata.fixSteps.length).toBeGreaterThan(0)
82
+
83
+ expect(metadata.evidence).toBeDefined()
84
+ expect(typeof metadata.evidence).toBe('string')
85
+ expect(metadata.evidence.length).toBeGreaterThan(0)
86
+
87
+ expect(metadata.references).toBeDefined()
88
+ expect(Array.isArray(metadata.references)).toBe(true)
89
+ expect(metadata.references.length).toBeGreaterThan(0)
90
+ }
91
+ })
92
+
93
+ it('should have valid OWASP/CWE reference URLs', () => {
94
+ for (const category of ALL_CATEGORIES) {
95
+ const metadata = RULE_REGISTRY[category]
96
+
97
+ for (const ref of metadata.references) {
98
+ expect(ref).toMatch(/^https?:\/\//)
99
+ // Should be OWASP, CWE, or other recognized security references
100
+ expect(
101
+ ref.includes('owasp.org') ||
102
+ ref.includes('cwe.mitre.org') ||
103
+ ref.includes('docker.com') ||
104
+ ref.includes('genai.owasp.org') ||
105
+ ref.includes('arxiv.org') || // Academic research
106
+ ref.includes('modelcontextprotocol.io') || // MCP official docs
107
+ ref.includes('invariantlabs.ai') || // Security research
108
+ ref.includes('snyk.io') || // Dependency security
109
+ ref.includes('osv.dev') || // Open Source Vulnerabilities
110
+ ref.includes('socket.dev') || // npm malware research
111
+ ref.includes('huggingface.co') || // ML model security docs
112
+ ref.includes('atlas.mitre.org') || // MITRE ATLAS (AI threat matrix)
113
+ ref.includes('docs.crewai.com') // CrewAI security docs
114
+ ).toBe(true)
115
+ }
116
+ }
117
+ })
118
+ })
119
+
120
+ describe('getRuleMetadata', () => {
121
+ it('should return metadata for valid category', () => {
122
+ const metadata = getRuleMetadata('sql_injection')
123
+
124
+ expect(metadata).toBeDefined()
125
+ expect(metadata?.name).toBe('SQL Injection')
126
+ expect(metadata?.whyItMatters).toContain('database')
127
+ expect(metadata?.fixSteps.length).toBeGreaterThan(0)
128
+ })
129
+
130
+ it('should return undefined for invalid category', () => {
131
+ // @ts-expect-error - testing invalid input
132
+ const metadata = getRuleMetadata('invalid_category')
133
+ expect(metadata).toBeUndefined()
134
+ })
135
+ })
136
+
137
+ describe('getAllCategories', () => {
138
+ it('should return all categories', () => {
139
+ const categories = getAllCategories()
140
+
141
+ expect(categories.length).toBe(ALL_CATEGORIES.length)
142
+ for (const category of ALL_CATEGORIES) {
143
+ expect(categories).toContain(category)
144
+ }
145
+ })
146
+ })
147
+
148
+ describe('hasMetadata', () => {
149
+ it('should return true for valid categories', () => {
150
+ expect(hasMetadata('hardcoded_secret')).toBe(true)
151
+ expect(hasMetadata('ai_prompt_injection')).toBe(true)
152
+ })
153
+
154
+ it('should return false for invalid categories', () => {
155
+ // @ts-expect-error - testing invalid input
156
+ expect(hasMetadata('invalid_category')).toBe(false)
157
+ })
158
+ })
159
+
160
+ describe('metadata quality', () => {
161
+ it('whyItMatters should explain business impact', () => {
162
+ // Check a few key categories have meaningful impact descriptions
163
+ const hardcodedSecret = RULE_REGISTRY.hardcoded_secret
164
+ expect(hardcodedSecret.whyItMatters).toMatch(/unauthorized|extract|access|credential/i)
165
+
166
+ const sqlInjection = RULE_REGISTRY.sql_injection
167
+ expect(sqlInjection.whyItMatters).toMatch(/read|modify|delete|database/i)
168
+
169
+ const missingAuth = RULE_REGISTRY.missing_auth
170
+ expect(missingAuth.whyItMatters).toMatch(/access|authentication|anyone/i)
171
+ })
172
+
173
+ it('fixSteps should be actionable', () => {
174
+ // Check fix steps are actionable (start with verbs)
175
+ for (const category of ALL_CATEGORIES) {
176
+ const metadata = RULE_REGISTRY[category]
177
+
178
+ for (const step of metadata.fixSteps) {
179
+ // Each step should start with an action verb (capitalized)
180
+ expect(step[0]).toBe(step[0].toUpperCase())
181
+ expect(step.length).toBeGreaterThan(10)
182
+ }
183
+ }
184
+ })
185
+
186
+ it('AI-specific categories should have OWASP LLM Top 10 references', () => {
187
+ const aiCategories: VulnerabilityCategory[] = [
188
+ 'ai_prompt_injection',
189
+ 'ai_unsafe_execution',
190
+ 'ai_overpermissive_tool',
191
+ 'ai_rag_exfiltration',
192
+ 'ai_endpoint_unprotected',
193
+ 'ai_schema_mismatch',
194
+ // AI Detection Roadmap Phase 1
195
+ 'ai_rag_corpus_poisoning',
196
+ 'ai_rag_pii_leakage',
197
+ 'ai_mcp_tool_poisoning',
198
+ 'ai_mcp_credential_issue',
199
+ 'ai_mcp_confused_deputy',
200
+ // AI Detection Roadmap Phase 2
201
+ 'ai_unsafe_model_load',
202
+ 'ai_unverified_model',
203
+ 'ai_unsafe_finetuning',
204
+ 'ai_excessive_agency',
205
+ ]
206
+
207
+ for (const category of aiCategories) {
208
+ const metadata = RULE_REGISTRY[category]
209
+ const hasLlmReference = metadata.references.some(
210
+ ref => ref.includes('genai.owasp.org') ||
211
+ ref.includes('large-language-model') ||
212
+ ref.includes('modelcontextprotocol.io') // MCP has its own docs
213
+ )
214
+ expect(hasLlmReference).toBe(true)
215
+ }
216
+ })
217
+ })
218
+ })
@@ -0,0 +1,470 @@
1
+ /**
2
+ * Framework-Aware Fix Suggestions Registry (PRO-83)
3
+ *
4
+ * Provides framework-specific fix suggestions that transform generic advice
5
+ * into actionable guidance based on the user's detected tech stack.
6
+ *
7
+ * When a Next.js + Prisma project has a SQL injection finding, this registry
8
+ * provides Prisma-specific fixes instead of generic SQL advice.
9
+ *
10
+ * Falls back gracefully to generic fixes (from metadata.ts) when no match.
11
+ */
12
+
13
+ import type { VulnerabilityCategory } from '../types'
14
+ import type { FrameworkContext, DataAccessContext } from '../utils/project-context-builder'
15
+
16
+ // ============================================================================
17
+ // Types
18
+ // ============================================================================
19
+
20
+ export type FrameworkKey =
21
+ | 'nextjs' | 'express' | 'fastify' | 'nestjs'
22
+ | 'prisma' | 'drizzle' | 'supabase' | 'mongoose' | 'knex'
23
+ | 'react' | 'vue'
24
+
25
+ export interface FrameworkFix {
26
+ /** Step-by-step fix instructions specific to this framework */
27
+ fixSteps: string[]
28
+ /** Optional code example demonstrating the fix */
29
+ codeExample?: string
30
+ }
31
+
32
+ // ============================================================================
33
+ // Framework Fix Registry
34
+ // ============================================================================
35
+
36
+ /**
37
+ * Registry mapping vulnerability categories to framework-specific fixes.
38
+ *
39
+ * Structure: category -> framework -> fix
40
+ *
41
+ * Priority categories (Tier 1):
42
+ * - sql_injection: Fixes vary dramatically by ORM
43
+ * - missing_auth: Framework-specific middleware patterns
44
+ * - xss: React/Vue/vanilla have different approaches
45
+ * - hardcoded_secret: Framework-specific env handling
46
+ * - cors_misconfiguration: Very framework-specific
47
+ */
48
+ export const FRAMEWORK_FIX_REGISTRY: Partial<Record<VulnerabilityCategory, Partial<Record<FrameworkKey, FrameworkFix>>>> = {
49
+ // ==========================================================================
50
+ // SQL Injection - Fixes vary dramatically by ORM
51
+ // ==========================================================================
52
+ sql_injection: {
53
+ prisma: {
54
+ fixSteps: [
55
+ 'Use Prisma query methods: prisma.user.findUnique({ where: { id } })',
56
+ 'For raw SQL, use Prisma.$queryRaw with template literals (auto-parameterized)',
57
+ 'Never concatenate user input into query strings',
58
+ 'Validate input with Zod before passing to queries',
59
+ ],
60
+ codeExample: `// Safe: Prisma parameterized query
61
+ const user = await prisma.user.findUnique({
62
+ where: { email: userInput }
63
+ })
64
+
65
+ // Safe: Raw query with parameters
66
+ await prisma.$queryRaw\`SELECT * FROM users WHERE id = \${userId}\``,
67
+ },
68
+ drizzle: {
69
+ fixSteps: [
70
+ 'Use Drizzle query builder with eq(), and(), or() operators',
71
+ 'For raw SQL, use sql`` template literal (auto-parameterized)',
72
+ 'Never concatenate user input into query strings',
73
+ 'Validate input with Zod before passing to queries',
74
+ ],
75
+ codeExample: `// Safe: Drizzle query builder
76
+ const users = await db.select().from(usersTable)
77
+ .where(eq(usersTable.email, userInput))
78
+
79
+ // Safe: Raw query with parameters
80
+ await db.execute(sql\`SELECT * FROM users WHERE id = \${userId}\`)`,
81
+ },
82
+ supabase: {
83
+ fixSteps: [
84
+ 'Use Supabase client methods: supabase.from("users").select().eq("id", userId)',
85
+ 'Rely on Row Level Security (RLS) for access control',
86
+ 'For complex queries, use stored procedures with parameters',
87
+ 'Never use .rpc() with string concatenation for query building',
88
+ ],
89
+ codeExample: `// Safe: Supabase client with RLS
90
+ const { data } = await supabase
91
+ .from('users')
92
+ .select('*')
93
+ .eq('email', userInput)
94
+ .single()`,
95
+ },
96
+ mongoose: {
97
+ fixSteps: [
98
+ 'Use Mongoose query methods with conditions object: User.find({ email: userInput })',
99
+ 'Avoid $where with user input - it allows JavaScript execution',
100
+ 'Validate ObjectIds before querying: mongoose.Types.ObjectId.isValid(id)',
101
+ 'Use schema validation to ensure expected data types',
102
+ ],
103
+ codeExample: `// Safe: Mongoose query with conditions
104
+ const user = await User.findOne({ email: userInput })
105
+
106
+ // Validate ObjectId before use
107
+ if (!mongoose.Types.ObjectId.isValid(id)) {
108
+ throw new Error('Invalid ID')
109
+ }`,
110
+ },
111
+ knex: {
112
+ fixSteps: [
113
+ 'Use Knex query builder: knex("users").where({ email: userInput })',
114
+ 'For raw SQL, use knex.raw() with parameter binding: knex.raw("SELECT * FROM users WHERE id = ?", [userId])',
115
+ 'Never use string concatenation in queries',
116
+ 'Validate input types before passing to queries',
117
+ ],
118
+ codeExample: `// Safe: Knex query builder
119
+ const user = await knex('users')
120
+ .where({ email: userInput })
121
+ .first()
122
+
123
+ // Safe: Raw query with parameters
124
+ await knex.raw('SELECT * FROM users WHERE id = ?', [userId])`,
125
+ },
126
+ },
127
+
128
+ // ==========================================================================
129
+ // Missing Authentication - Framework-specific middleware patterns
130
+ // ==========================================================================
131
+ missing_auth: {
132
+ nextjs: {
133
+ fixSteps: [
134
+ 'Add auth check in middleware.ts with matcher config for protected routes',
135
+ 'Call auth() or getServerSession() at the start of route handlers',
136
+ 'Return 401/redirect for unauthenticated requests',
137
+ 'Use throwing auth helpers (getCurrentUserId) that guarantee authenticated context',
138
+ ],
139
+ codeExample: `// middleware.ts
140
+ export { auth as middleware } from '@/auth'
141
+ export const config = { matcher: ['/dashboard/:path*', '/api/:path*'] }
142
+
143
+ // In route handler
144
+ import { auth } from '@/auth'
145
+
146
+ export async function GET() {
147
+ const session = await auth()
148
+ if (!session) {
149
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
150
+ }
151
+ // ... authenticated code
152
+ }`,
153
+ },
154
+ express: {
155
+ fixSteps: [
156
+ 'Use authentication middleware (passport, express-session, or custom)',
157
+ 'Apply middleware to routes: app.use("/api", authMiddleware)',
158
+ 'Verify JWT tokens or session cookies in middleware',
159
+ 'Return 401 status for unauthenticated requests',
160
+ ],
161
+ codeExample: `// authMiddleware.js
162
+ const authMiddleware = (req, res, next) => {
163
+ const token = req.headers.authorization?.split(' ')[1]
164
+ if (!token) {
165
+ return res.status(401).json({ error: 'Unauthorized' })
166
+ }
167
+ try {
168
+ req.user = jwt.verify(token, process.env.JWT_SECRET)
169
+ next()
170
+ } catch {
171
+ return res.status(401).json({ error: 'Invalid token' })
172
+ }
173
+ }
174
+
175
+ // Apply to routes
176
+ app.use('/api', authMiddleware)`,
177
+ },
178
+ fastify: {
179
+ fixSteps: [
180
+ 'Use @fastify/auth or @fastify/jwt for authentication',
181
+ 'Register auth plugin and apply to routes with onRequest hook',
182
+ 'Decorate request with user info after authentication',
183
+ 'Return 401 status for unauthenticated requests',
184
+ ],
185
+ codeExample: `// Register JWT plugin
186
+ await fastify.register(fastifyJwt, { secret: process.env.JWT_SECRET })
187
+
188
+ // Auth decorator
189
+ fastify.decorate('authenticate', async (request, reply) => {
190
+ try {
191
+ await request.jwtVerify()
192
+ } catch (err) {
193
+ reply.code(401).send({ error: 'Unauthorized' })
194
+ }
195
+ })
196
+
197
+ // Protected route
198
+ fastify.get('/api/data', { onRequest: [fastify.authenticate] }, handler)`,
199
+ },
200
+ nestjs: {
201
+ fixSteps: [
202
+ 'Use @nestjs/passport with Guards for authentication',
203
+ 'Apply AuthGuard to controllers or routes with @UseGuards()',
204
+ 'Implement custom guards for role-based access control',
205
+ 'Use @Public() decorator for intentionally public endpoints',
206
+ ],
207
+ codeExample: `// auth.guard.ts
208
+ @Injectable()
209
+ export class JwtAuthGuard extends AuthGuard('jwt') {}
210
+
211
+ // controller.ts
212
+ @Controller('api')
213
+ @UseGuards(JwtAuthGuard)
214
+ export class ApiController {
215
+ @Get('data')
216
+ getData(@Request() req) {
217
+ return this.service.getData(req.user.id)
218
+ }
219
+ }`,
220
+ },
221
+ },
222
+
223
+ // ==========================================================================
224
+ // XSS - React/Vue/vanilla have different approaches
225
+ // ==========================================================================
226
+ xss: {
227
+ react: {
228
+ fixSteps: [
229
+ 'Use JSX expressions {variable} - React auto-escapes by default',
230
+ 'Avoid dangerouslySetInnerHTML unless absolutely necessary',
231
+ 'If you must use dangerouslySetInnerHTML, sanitize with DOMPurify first',
232
+ 'Validate and sanitize user input on the server side as well',
233
+ ],
234
+ codeExample: `// Safe: JSX auto-escapes
235
+ function Comment({ text }) {
236
+ return <div>{text}</div> // Safe - auto-escaped
237
+ }
238
+
239
+ // If HTML is required, sanitize first
240
+ import DOMPurify from 'dompurify'
241
+
242
+ function RichContent({ html }) {
243
+ const clean = DOMPurify.sanitize(html)
244
+ return <div dangerouslySetInnerHTML={{ __html: clean }} />
245
+ }`,
246
+ },
247
+ vue: {
248
+ fixSteps: [
249
+ 'Use v-text or {{ }} interpolation - Vue auto-escapes by default',
250
+ 'Avoid v-html with user-controlled content',
251
+ 'If you must use v-html, sanitize with DOMPurify first',
252
+ 'Use Content Security Policy headers as additional protection',
253
+ ],
254
+ codeExample: `<!-- Safe: Vue interpolation auto-escapes -->
255
+ <template>
256
+ <div>{{ userContent }}</div>
257
+ </template>
258
+
259
+ <!-- If HTML is required, sanitize first -->
260
+ <script setup>
261
+ import DOMPurify from 'dompurify'
262
+ const sanitizedHtml = computed(() => DOMPurify.sanitize(props.html))
263
+ </script>
264
+
265
+ <template>
266
+ <div v-html="sanitizedHtml"></div>
267
+ </template>`,
268
+ },
269
+ },
270
+
271
+ // ==========================================================================
272
+ // Hardcoded Secrets - Framework-specific env handling
273
+ // ==========================================================================
274
+ hardcoded_secret: {
275
+ nextjs: {
276
+ fixSteps: [
277
+ 'Store secrets in .env.local (gitignored by default)',
278
+ 'Access via process.env.SECRET_NAME in server components/API routes',
279
+ 'Only use NEXT_PUBLIC_ prefix for intentionally client-exposed values',
280
+ 'Use Vercel Environment Variables for production deployments',
281
+ ],
282
+ codeExample: `// .env.local
283
+ DATABASE_URL=postgres://...
284
+ API_SECRET=your-secret-here
285
+
286
+ // Server-side code (API route, server component)
287
+ const secret = process.env.API_SECRET
288
+
289
+ // Client-safe public values only
290
+ // NEXT_PUBLIC_ANALYTICS_ID=xxx`,
291
+ },
292
+ express: {
293
+ fixSteps: [
294
+ 'Use dotenv package to load from .env files',
295
+ 'Add .env to .gitignore immediately',
296
+ 'Access secrets via process.env.SECRET_NAME',
297
+ 'Use different .env files per environment (.env.production)',
298
+ ],
299
+ codeExample: `// At app entry point
300
+ import 'dotenv/config'
301
+
302
+ // .env (gitignored)
303
+ DATABASE_URL=postgres://...
304
+ JWT_SECRET=your-secret-here
305
+
306
+ // Access in code
307
+ const jwtSecret = process.env.JWT_SECRET
308
+ if (!jwtSecret) throw new Error('JWT_SECRET required')`,
309
+ },
310
+ },
311
+
312
+ // ==========================================================================
313
+ // CORS Misconfiguration - Very framework-specific
314
+ // ==========================================================================
315
+ cors_misconfiguration: {
316
+ nextjs: {
317
+ fixSteps: [
318
+ 'Configure CORS in next.config.js headers or middleware',
319
+ 'Specify exact allowed origins instead of wildcard "*"',
320
+ 'Use environment variables for origin configuration',
321
+ 'Consider using middleware for dynamic origin validation',
322
+ ],
323
+ codeExample: `// next.config.js
324
+ module.exports = {
325
+ async headers() {
326
+ return [{
327
+ source: '/api/:path*',
328
+ headers: [
329
+ { key: 'Access-Control-Allow-Origin', value: process.env.ALLOWED_ORIGIN },
330
+ { key: 'Access-Control-Allow-Methods', value: 'GET,POST,PUT,DELETE' },
331
+ { key: 'Access-Control-Allow-Headers', value: 'Content-Type,Authorization' },
332
+ ],
333
+ }]
334
+ },
335
+ }
336
+
337
+ // Or in middleware.ts for dynamic validation
338
+ const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']`,
339
+ },
340
+ express: {
341
+ fixSteps: [
342
+ 'Use the cors package with specific origin configuration',
343
+ 'Replace origin: "*" with an allowlist of origins',
344
+ 'Use a function for dynamic origin validation',
345
+ 'Disable credentials for cross-origin requests unless necessary',
346
+ ],
347
+ codeExample: `import cors from 'cors'
348
+
349
+ const allowedOrigins = ['https://app.example.com', 'https://admin.example.com']
350
+
351
+ app.use(cors({
352
+ origin: (origin, callback) => {
353
+ if (!origin || allowedOrigins.includes(origin)) {
354
+ callback(null, true)
355
+ } else {
356
+ callback(new Error('Not allowed by CORS'))
357
+ }
358
+ },
359
+ credentials: true, // Only if you need cookies/auth headers
360
+ }))`,
361
+ },
362
+ fastify: {
363
+ fixSteps: [
364
+ 'Use @fastify/cors with specific origin configuration',
365
+ 'Specify allowed origins instead of true/wildcard',
366
+ 'Use a function for dynamic origin validation',
367
+ 'Configure allowed methods and headers explicitly',
368
+ ],
369
+ codeExample: `import cors from '@fastify/cors'
370
+
371
+ const allowedOrigins = ['https://app.example.com']
372
+
373
+ await fastify.register(cors, {
374
+ origin: (origin, cb) => {
375
+ if (!origin || allowedOrigins.includes(origin)) {
376
+ cb(null, true)
377
+ } else {
378
+ cb(new Error('Not allowed'), false)
379
+ }
380
+ },
381
+ credentials: true,
382
+ })`,
383
+ },
384
+ },
385
+
386
+ // ==========================================================================
387
+ // Dangerous Function (eval, innerHTML) - Context-specific
388
+ // ==========================================================================
389
+ dangerous_function: {
390
+ react: {
391
+ fixSteps: [
392
+ 'Replace innerHTML with React JSX (auto-escapes content)',
393
+ 'Use textContent or innerText for plain text updates',
394
+ 'If dynamic HTML is required, use DOMPurify before dangerouslySetInnerHTML',
395
+ 'Consider using a markdown renderer (react-markdown) for rich content',
396
+ ],
397
+ codeExample: `// Instead of innerHTML
398
+ const element = document.getElementById('content')
399
+ element.innerHTML = userInput // Dangerous!
400
+
401
+ // Use React JSX
402
+ function SafeContent({ content }) {
403
+ return <div>{content}</div> // Safe - auto-escaped
404
+ }`,
405
+ },
406
+ vue: {
407
+ fixSteps: [
408
+ 'Use Vue template interpolation {{ }} instead of v-html',
409
+ 'If HTML rendering is required, sanitize with DOMPurify first',
410
+ 'Consider component-based approaches for dynamic content',
411
+ 'Validate and sanitize on the server before sending to client',
412
+ ],
413
+ },
414
+ },
415
+ }
416
+
417
+ // ============================================================================
418
+ // Lookup Function
419
+ // ============================================================================
420
+
421
+ /**
422
+ * Get framework-specific fix for a vulnerability category.
423
+ *
424
+ * Priority order for ORM-related categories (sql_injection):
425
+ * 1. ORM (prisma, drizzle, supabase, mongoose, knex)
426
+ * 2. Backend framework (nextjs, express, fastify, nestjs)
427
+ *
428
+ * Priority order for other categories:
429
+ * 1. Frontend framework for XSS (react, vue)
430
+ * 2. Backend framework for auth/cors/secrets
431
+ *
432
+ * @returns FrameworkFix if a match is found, undefined otherwise (falls back to generic)
433
+ */
434
+ export function getFrameworkFix(
435
+ category: VulnerabilityCategory,
436
+ frameworks: FrameworkContext,
437
+ dataAccess?: DataAccessContext
438
+ ): FrameworkFix | undefined {
439
+ const categoryFixes = FRAMEWORK_FIX_REGISTRY[category]
440
+ if (!categoryFixes) {
441
+ return undefined
442
+ }
443
+
444
+ // For SQL injection, prioritize ORM-specific fixes
445
+ if (category === 'sql_injection' && dataAccess?.orm) {
446
+ const ormFix = categoryFixes[dataAccess.orm as FrameworkKey]
447
+ if (ormFix) {
448
+ return ormFix
449
+ }
450
+ }
451
+
452
+ // For XSS and dangerous_function, prioritize frontend framework
453
+ if ((category === 'xss' || category === 'dangerous_function') && frameworks.frontend) {
454
+ const frontendFix = categoryFixes[frameworks.frontend as FrameworkKey]
455
+ if (frontendFix) {
456
+ return frontendFix
457
+ }
458
+ }
459
+
460
+ // For other categories, try backend framework
461
+ if (frameworks.primary) {
462
+ const backendFix = categoryFixes[frameworks.primary as FrameworkKey]
463
+ if (backendFix) {
464
+ return backendFix
465
+ }
466
+ }
467
+
468
+ // No framework-specific fix found
469
+ return undefined
470
+ }