@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,462 @@
1
+ /**
2
+ * Suppression Config Loader
3
+ * Loads and validates suppression configuration from various file formats
4
+ */
5
+
6
+ import { readFileSync, writeFileSync, existsSync } from 'fs'
7
+ import { join, dirname } from 'path'
8
+ import * as yaml from 'js-yaml'
9
+ import type {
10
+ SuppressionConfig,
11
+ RuleSuppression,
12
+ FindingSuppression,
13
+ } from './types'
14
+ import {
15
+ SUPPRESSION_CONFIG_FILES,
16
+ DEFAULT_SUPPRESSION_CONFIG,
17
+ } from './types'
18
+ import type { VulnerabilityCategory } from '../types'
19
+
20
+ /**
21
+ * Result of loading suppression config
22
+ */
23
+ export interface ConfigLoadResult {
24
+ /** The loaded config (or default if none found) */
25
+ config: SuppressionConfig
26
+ /** Path to the config file (if found) */
27
+ configPath?: string
28
+ /** Whether a config file was found */
29
+ found: boolean
30
+ /** Any errors encountered during loading */
31
+ errors: string[]
32
+ }
33
+
34
+ /**
35
+ * Find the config file by walking up the directory tree
36
+ */
37
+ export function findConfigFile(startPath: string): string | null {
38
+ let currentDir = startPath
39
+
40
+ // Walk up directory tree looking for config file
41
+ while (currentDir !== dirname(currentDir)) {
42
+ for (const filename of SUPPRESSION_CONFIG_FILES) {
43
+ const configPath = join(currentDir, filename)
44
+ if (existsSync(configPath)) {
45
+ return configPath
46
+ }
47
+ }
48
+ currentDir = dirname(currentDir)
49
+ }
50
+
51
+ // Check root directory one more time
52
+ for (const filename of SUPPRESSION_CONFIG_FILES) {
53
+ const configPath = join(currentDir, filename)
54
+ if (existsSync(configPath)) {
55
+ return configPath
56
+ }
57
+ }
58
+
59
+ return null
60
+ }
61
+
62
+ /**
63
+ * Parse config file content based on extension
64
+ */
65
+ function parseConfigContent(content: string, filePath: string): unknown {
66
+ const isYaml = filePath.endsWith('.yaml') || filePath.endsWith('.yml')
67
+
68
+ if (isYaml) {
69
+ return yaml.load(content)
70
+ }
71
+
72
+ // JSON or .oculumrc (treat as JSON)
73
+ return JSON.parse(content)
74
+ }
75
+
76
+ /**
77
+ * Validate suppression config structure
78
+ */
79
+ function validateConfig(data: unknown): { valid: boolean; errors: string[] } {
80
+ const errors: string[] = []
81
+
82
+ if (!data || typeof data !== 'object') {
83
+ return { valid: false, errors: ['Config must be an object'] }
84
+ }
85
+
86
+ const config = data as Record<string, unknown>
87
+
88
+ // Validate version
89
+ if (config.version !== undefined && config.version !== 1) {
90
+ errors.push(`Unsupported config version: ${config.version}. Only version 1 is supported.`)
91
+ }
92
+
93
+ // Validate suppressions
94
+ if (config.suppressions !== undefined) {
95
+ if (typeof config.suppressions !== 'object' || config.suppressions === null) {
96
+ errors.push('suppressions must be an object')
97
+ } else {
98
+ const suppressions = config.suppressions as Record<string, unknown>
99
+
100
+ // Validate rules
101
+ if (suppressions.rules !== undefined) {
102
+ if (!Array.isArray(suppressions.rules)) {
103
+ errors.push('suppressions.rules must be an array')
104
+ } else {
105
+ suppressions.rules.forEach((rule, i) => {
106
+ if (!rule || typeof rule !== 'object') {
107
+ errors.push(`suppressions.rules[${i}] must be an object`)
108
+ } else {
109
+ const r = rule as Record<string, unknown>
110
+ if (!r.category || typeof r.category !== 'string') {
111
+ errors.push(`suppressions.rules[${i}].category is required`)
112
+ }
113
+ if (!r.reason || typeof r.reason !== 'string') {
114
+ errors.push(`suppressions.rules[${i}].reason is required`)
115
+ }
116
+ if (r.expires && typeof r.expires !== 'string') {
117
+ errors.push(`suppressions.rules[${i}].expires must be a string (ISO date)`)
118
+ }
119
+ if (r.paths && !Array.isArray(r.paths)) {
120
+ errors.push(`suppressions.rules[${i}].paths must be an array`)
121
+ }
122
+ }
123
+ })
124
+ }
125
+ }
126
+
127
+ // Validate findings
128
+ if (suppressions.findings !== undefined) {
129
+ if (!Array.isArray(suppressions.findings)) {
130
+ errors.push('suppressions.findings must be an array')
131
+ } else {
132
+ suppressions.findings.forEach((finding, i) => {
133
+ if (!finding || typeof finding !== 'object') {
134
+ errors.push(`suppressions.findings[${i}] must be an object`)
135
+ } else {
136
+ const f = finding as Record<string, unknown>
137
+ if (!f.hash || typeof f.hash !== 'string') {
138
+ errors.push(`suppressions.findings[${i}].hash is required`)
139
+ }
140
+ if (!f.file || typeof f.file !== 'string') {
141
+ errors.push(`suppressions.findings[${i}].file is required`)
142
+ }
143
+ if (!f.reason || typeof f.reason !== 'string') {
144
+ errors.push(`suppressions.findings[${i}].reason is required`)
145
+ }
146
+ if (f.expires && typeof f.expires !== 'string') {
147
+ errors.push(`suppressions.findings[${i}].expires must be a string (ISO date)`)
148
+ }
149
+ }
150
+ })
151
+ }
152
+ }
153
+ }
154
+ }
155
+
156
+ // Validate ignore patterns
157
+ if (config.ignore !== undefined) {
158
+ if (!Array.isArray(config.ignore)) {
159
+ errors.push('ignore must be an array of strings')
160
+ } else {
161
+ config.ignore.forEach((pattern, i) => {
162
+ if (typeof pattern !== 'string') {
163
+ errors.push(`ignore[${i}] must be a string`)
164
+ }
165
+ })
166
+ }
167
+ }
168
+
169
+ return { valid: errors.length === 0, errors }
170
+ }
171
+
172
+ /**
173
+ * Load suppression config from a specific path
174
+ */
175
+ export function loadConfigFromPath(configPath: string): ConfigLoadResult {
176
+ const errors: string[] = []
177
+
178
+ try {
179
+ const content = readFileSync(configPath, 'utf-8')
180
+ const data = parseConfigContent(content, configPath)
181
+
182
+ const validation = validateConfig(data)
183
+ if (!validation.valid) {
184
+ return {
185
+ config: DEFAULT_SUPPRESSION_CONFIG,
186
+ configPath,
187
+ found: true,
188
+ errors: validation.errors,
189
+ }
190
+ }
191
+
192
+ // Cast to config type (after validation)
193
+ const config = data as SuppressionConfig
194
+
195
+ // Ensure defaults
196
+ return {
197
+ config: {
198
+ version: config.version || 1,
199
+ suppressions: {
200
+ rules: config.suppressions?.rules || [],
201
+ findings: config.suppressions?.findings || [],
202
+ },
203
+ ignore: config.ignore || [],
204
+ },
205
+ configPath,
206
+ found: true,
207
+ errors,
208
+ }
209
+ } catch (err) {
210
+ const message = err instanceof Error ? err.message : String(err)
211
+ return {
212
+ config: DEFAULT_SUPPRESSION_CONFIG,
213
+ configPath,
214
+ found: true,
215
+ errors: [`Failed to parse config: ${message}`],
216
+ }
217
+ }
218
+ }
219
+
220
+ /**
221
+ * Load suppression config from a project directory
222
+ * Searches up the directory tree for a config file
223
+ */
224
+ export function loadSuppressionConfig(projectPath: string): ConfigLoadResult {
225
+ const configPath = findConfigFile(projectPath)
226
+
227
+ if (!configPath) {
228
+ return {
229
+ config: DEFAULT_SUPPRESSION_CONFIG,
230
+ found: false,
231
+ errors: [],
232
+ }
233
+ }
234
+
235
+ return loadConfigFromPath(configPath)
236
+ }
237
+
238
+ /**
239
+ * Write suppression config to a file
240
+ */
241
+ export function writeSuppressionConfig(
242
+ configPath: string,
243
+ config: SuppressionConfig
244
+ ): void {
245
+ const isYaml = configPath.endsWith('.yaml') || configPath.endsWith('.yml')
246
+
247
+ let content: string
248
+ if (isYaml) {
249
+ content = yaml.dump(config, {
250
+ indent: 2,
251
+ lineWidth: 100,
252
+ noRefs: true,
253
+ sortKeys: false,
254
+ })
255
+ } else {
256
+ content = JSON.stringify(config, null, 2) + '\n'
257
+ }
258
+
259
+ writeFileSync(configPath, content, 'utf-8')
260
+ }
261
+
262
+ /**
263
+ * Add a finding suppression to the config file
264
+ */
265
+ export function addFindingSuppression(
266
+ projectPath: string,
267
+ suppression: FindingSuppression
268
+ ): { success: boolean; configPath: string; error?: string } {
269
+ // Find or create config file
270
+ let configPath = findConfigFile(projectPath)
271
+ let config: SuppressionConfig
272
+
273
+ if (configPath) {
274
+ const result = loadConfigFromPath(configPath)
275
+ if (result.errors.length > 0) {
276
+ return {
277
+ success: false,
278
+ configPath: configPath,
279
+ error: result.errors.join(', '),
280
+ }
281
+ }
282
+ config = result.config
283
+ } else {
284
+ // Create new config file in project root
285
+ configPath = join(projectPath, '.oculum.yaml')
286
+ config = { ...DEFAULT_SUPPRESSION_CONFIG }
287
+ }
288
+
289
+ // Initialize suppressions if needed
290
+ if (!config.suppressions) {
291
+ config.suppressions = {}
292
+ }
293
+ if (!config.suppressions.findings) {
294
+ config.suppressions.findings = []
295
+ }
296
+
297
+ // Check for duplicate
298
+ const existingIndex = config.suppressions.findings.findIndex(
299
+ f => f.hash === suppression.hash
300
+ )
301
+
302
+ if (existingIndex >= 0) {
303
+ // Update existing suppression
304
+ config.suppressions.findings[existingIndex] = suppression
305
+ } else {
306
+ // Add new suppression
307
+ config.suppressions.findings.push(suppression)
308
+ }
309
+
310
+ // Write config
311
+ try {
312
+ writeSuppressionConfig(configPath, config)
313
+ return { success: true, configPath }
314
+ } catch (err) {
315
+ const message = err instanceof Error ? err.message : String(err)
316
+ return { success: false, configPath, error: message }
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Remove a finding suppression from the config file
322
+ */
323
+ export function removeFindingSuppression(
324
+ projectPath: string,
325
+ hash: string
326
+ ): { success: boolean; configPath?: string; error?: string; removed: boolean } {
327
+ const configPath = findConfigFile(projectPath)
328
+
329
+ if (!configPath) {
330
+ return { success: true, removed: false }
331
+ }
332
+
333
+ const result = loadConfigFromPath(configPath)
334
+ if (result.errors.length > 0) {
335
+ return {
336
+ success: false,
337
+ configPath,
338
+ error: result.errors.join(', '),
339
+ removed: false,
340
+ }
341
+ }
342
+
343
+ const config = result.config
344
+
345
+ if (!config.suppressions?.findings) {
346
+ return { success: true, configPath, removed: false }
347
+ }
348
+
349
+ const initialLength = config.suppressions.findings.length
350
+ config.suppressions.findings = config.suppressions.findings.filter(
351
+ f => f.hash !== hash
352
+ )
353
+
354
+ const removed = config.suppressions.findings.length < initialLength
355
+
356
+ if (removed) {
357
+ try {
358
+ writeSuppressionConfig(configPath, config)
359
+ } catch (err) {
360
+ const message = err instanceof Error ? err.message : String(err)
361
+ return { success: false, configPath, error: message, removed: false }
362
+ }
363
+ }
364
+
365
+ return { success: true, configPath, removed }
366
+ }
367
+
368
+ /**
369
+ * Add a rule suppression to the config file
370
+ */
371
+ export function addRuleSuppression(
372
+ projectPath: string,
373
+ suppression: RuleSuppression
374
+ ): { success: boolean; configPath: string; error?: string } {
375
+ // Find or create config file
376
+ let configPath = findConfigFile(projectPath)
377
+ let config: SuppressionConfig
378
+
379
+ if (configPath) {
380
+ const result = loadConfigFromPath(configPath)
381
+ if (result.errors.length > 0) {
382
+ return {
383
+ success: false,
384
+ configPath: configPath,
385
+ error: result.errors.join(', '),
386
+ }
387
+ }
388
+ config = result.config
389
+ } else {
390
+ // Create new config file in project root
391
+ configPath = join(projectPath, '.oculum.yaml')
392
+ config = { ...DEFAULT_SUPPRESSION_CONFIG }
393
+ }
394
+
395
+ // Initialize suppressions if needed
396
+ if (!config.suppressions) {
397
+ config.suppressions = {}
398
+ }
399
+ if (!config.suppressions.rules) {
400
+ config.suppressions.rules = []
401
+ }
402
+
403
+ // Check for duplicate (same category and paths)
404
+ const existingIndex = config.suppressions.rules.findIndex(
405
+ r => r.category === suppression.category &&
406
+ JSON.stringify(r.paths || []) === JSON.stringify(suppression.paths || [])
407
+ )
408
+
409
+ if (existingIndex >= 0) {
410
+ // Update existing suppression
411
+ config.suppressions.rules[existingIndex] = suppression
412
+ } else {
413
+ // Add new suppression
414
+ config.suppressions.rules.push(suppression)
415
+ }
416
+
417
+ // Write config
418
+ try {
419
+ writeSuppressionConfig(configPath, config)
420
+ return { success: true, configPath }
421
+ } catch (err) {
422
+ const message = err instanceof Error ? err.message : String(err)
423
+ return { success: false, configPath, error: message }
424
+ }
425
+ }
426
+
427
+ /**
428
+ * List all suppressions in the config
429
+ */
430
+ export function listSuppressions(projectPath: string): {
431
+ rules: RuleSuppression[]
432
+ findings: FindingSuppression[]
433
+ configPath?: string
434
+ errors: string[]
435
+ } {
436
+ const result = loadSuppressionConfig(projectPath)
437
+
438
+ return {
439
+ rules: result.config.suppressions?.rules || [],
440
+ findings: result.config.suppressions?.findings || [],
441
+ configPath: result.configPath,
442
+ errors: result.errors,
443
+ }
444
+ }
445
+
446
+ /**
447
+ * Check if a date string is expired
448
+ */
449
+ export function isExpired(dateString: string | undefined): boolean {
450
+ if (!dateString) {
451
+ return false
452
+ }
453
+
454
+ try {
455
+ const expirationDate = new Date(dateString)
456
+ const now = new Date()
457
+ return expirationDate < now
458
+ } catch {
459
+ // Invalid date format - treat as not expired
460
+ return false
461
+ }
462
+ }
@@ -0,0 +1,95 @@
1
+ /**
2
+ * Finding Hash Computation
3
+ * Creates stable, reproducible hashes for identifying specific findings
4
+ */
5
+
6
+ import { createHash } from 'crypto'
7
+ import type { Vulnerability } from '../types'
8
+
9
+ /**
10
+ * Normalize a file path for hashing
11
+ * - Removes leading ./ or /
12
+ * - Converts backslashes to forward slashes
13
+ * - Lowercases (for case-insensitive matching)
14
+ */
15
+ export function normalizePathForHash(filePath: string): string {
16
+ return filePath
17
+ .replace(/^\.\//, '') // Remove leading ./
18
+ .replace(/^\//, '') // Remove leading /
19
+ .replace(/\\/g, '/') // Normalize backslashes
20
+ .toLowerCase()
21
+ }
22
+
23
+ /**
24
+ * Normalize line content for hashing
25
+ * - Trims whitespace
26
+ * - Collapses multiple whitespace to single space
27
+ * - Removes common noise (line numbers, etc.)
28
+ */
29
+ export function normalizeContentForHash(content: string): string {
30
+ return content
31
+ .trim()
32
+ .replace(/\s+/g, ' ') // Collapse whitespace
33
+ .replace(/^\d+:\s*/, '') // Remove line number prefix if present
34
+ }
35
+
36
+ /**
37
+ * Compute a stable hash for a vulnerability finding
38
+ *
39
+ * Hash = SHA256(normalizedPath:normalizedContent:category).slice(0,16)
40
+ *
41
+ * This creates a 16-character hex hash that:
42
+ * - Stays stable across whitespace changes
43
+ * - Changes when the actual code changes
44
+ * - Changes when the finding category changes
45
+ * - Is unique enough for practical use
46
+ *
47
+ * @param finding - The vulnerability to hash
48
+ * @returns A 16-character hex hash string
49
+ */
50
+ export function computeFindingHash(finding: Vulnerability): string {
51
+ const normalizedPath = normalizePathForHash(finding.filePath)
52
+ const normalizedContent = normalizeContentForHash(finding.lineContent)
53
+ const category = finding.category
54
+
55
+ const input = `${normalizedPath}:${normalizedContent}:${category}`
56
+ const hash = createHash('sha256').update(input).digest('hex')
57
+
58
+ // Return first 16 characters (64 bits of entropy - sufficient for uniqueness)
59
+ return hash.slice(0, 16)
60
+ }
61
+
62
+ /**
63
+ * Compute hash from raw components (for testing or manual construction)
64
+ */
65
+ export function computeHashFromComponents(
66
+ filePath: string,
67
+ lineContent: string,
68
+ category: string
69
+ ): string {
70
+ const normalizedPath = normalizePathForHash(filePath)
71
+ const normalizedContent = normalizeContentForHash(lineContent)
72
+
73
+ const input = `${normalizedPath}:${normalizedContent}:${category}`
74
+ const hash = createHash('sha256').update(input).digest('hex')
75
+
76
+ return hash.slice(0, 16)
77
+ }
78
+
79
+ /**
80
+ * Validate a hash format
81
+ * Must be 16 hexadecimal characters
82
+ */
83
+ export function isValidHash(hash: string): boolean {
84
+ return /^[0-9a-f]{16}$/i.test(hash)
85
+ }
86
+
87
+ /**
88
+ * Format a hash for display (with truncation indicator)
89
+ */
90
+ export function formatHashForDisplay(hash: string): string {
91
+ if (hash.length <= 16) {
92
+ return hash
93
+ }
94
+ return `${hash.slice(0, 12)}...`
95
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Suppression System
3
+ * Allows users to suppress findings via config files and inline comments
4
+ */
5
+
6
+ // Main exports
7
+ export { SuppressionManager, type SuppressionManagerOptions } from './manager'
8
+
9
+ // Types
10
+ export type {
11
+ SuppressionConfig,
12
+ RuleSuppression,
13
+ FindingSuppression,
14
+ InlineSuppression,
15
+ SuppressionMatch,
16
+ SuppressedVulnerability,
17
+ SuppressionResult,
18
+ } from './types'
19
+ export { SUPPRESSION_CONFIG_FILES, DEFAULT_SUPPRESSION_CONFIG } from './types'
20
+
21
+ // Hash utilities
22
+ export {
23
+ computeFindingHash,
24
+ computeHashFromComponents,
25
+ normalizePathForHash,
26
+ normalizeContentForHash,
27
+ isValidHash,
28
+ formatHashForDisplay,
29
+ } from './hash'
30
+
31
+ // Config utilities
32
+ export {
33
+ loadSuppressionConfig,
34
+ loadConfigFromPath,
35
+ findConfigFile,
36
+ writeSuppressionConfig,
37
+ addFindingSuppression,
38
+ removeFindingSuppression,
39
+ addRuleSuppression,
40
+ listSuppressions,
41
+ isExpired,
42
+ type ConfigLoadResult,
43
+ } from './config-loader'
44
+
45
+ // Inline comment utilities
46
+ export {
47
+ parseInlineSuppressions,
48
+ isLineSuppressed,
49
+ extractSuppressionReasons,
50
+ generateSuppressionComment,
51
+ } from './inline-parser'