@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,273 @@
1
+ /**
2
+ * Inline Suppression Comment Parser
3
+ * Parses oculum-ignore comments from source code
4
+ */
5
+
6
+ import type { InlineSuppression } from './types'
7
+ import type { VulnerabilityCategory } from '../types'
8
+
9
+ /**
10
+ * Valid suppression comment patterns:
11
+ *
12
+ * Single-line suppressions:
13
+ * // oculum-ignore-next-line: reason here
14
+ * // oculum-ignore-next-line [rule-id]: reason here
15
+ * code // oculum-ignore: reason here
16
+ * code // oculum-ignore [rule-id]: reason here
17
+ *
18
+ * Block suppressions:
19
+ * /* oculum-ignore-block: reason here * /
20
+ * ... code ...
21
+ * /* oculum-ignore-block-end * /
22
+ *
23
+ * Also supports:
24
+ * # oculum-ignore-next-line: reason (Python, Ruby, YAML, etc.)
25
+ * -- oculum-ignore-next-line: reason (SQL)
26
+ */
27
+
28
+ // Regex patterns for parsing comments
29
+ const PATTERNS = {
30
+ // // oculum-ignore-next-line: reason
31
+ // // oculum-ignore-next-line [rule-id]: reason
32
+ nextLine: /(?:\/\/|#|--)\s*oculum-ignore-next-line(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,
33
+
34
+ // code // oculum-ignore: reason
35
+ // code // oculum-ignore [rule-id]: reason
36
+ sameLine: /(?:\/\/|#|--)\s*oculum-ignore(?:\s*\[([^\]]+)\])?\s*:\s*(.+)/i,
37
+
38
+ // /* oculum-ignore-block: reason */
39
+ // /* oculum-ignore-block [rule-id]: reason */
40
+ blockStart: /\/\*\s*oculum-ignore-block(?:\s*\[([^\]]+)\])?\s*:\s*([^*]+)\*\//i,
41
+
42
+ // /* oculum-ignore-block-end */
43
+ blockEnd: /\/\*\s*oculum-ignore-block-end\s*\*\//i,
44
+ }
45
+
46
+ /**
47
+ * Parse a single line for suppression comments
48
+ */
49
+ function parseLineForSuppression(
50
+ line: string,
51
+ lineNumber: number
52
+ ): InlineSuppression | null {
53
+ // Check for next-line suppression (must be the only thing on the line, aside from whitespace)
54
+ const trimmed = line.trim()
55
+
56
+ // Next-line pattern (comment at start of line)
57
+ if (
58
+ trimmed.startsWith('//') ||
59
+ trimmed.startsWith('#') ||
60
+ trimmed.startsWith('--')
61
+ ) {
62
+ const nextLineMatch = trimmed.match(PATTERNS.nextLine)
63
+ if (nextLineMatch) {
64
+ return {
65
+ lineNumber,
66
+ type: 'next-line',
67
+ ruleId: nextLineMatch[1] as VulnerabilityCategory | undefined,
68
+ reason: nextLineMatch[2].trim(),
69
+ commentText: trimmed,
70
+ }
71
+ }
72
+ }
73
+
74
+ // Same-line pattern (comment at end of line with code before)
75
+ // Only check if there's actual code before the comment
76
+ const sameLineMatch = line.match(PATTERNS.sameLine)
77
+ if (sameLineMatch) {
78
+ // Find where the comment starts
79
+ const commentIndex = line.search(/(?:\/\/|#|--)\s*oculum-ignore/i)
80
+ const beforeComment = line.substring(0, commentIndex).trim()
81
+
82
+ // Only treat as same-line if there's code before the comment
83
+ // (not just whitespace or if it's at the start)
84
+ if (beforeComment.length > 0) {
85
+ return {
86
+ lineNumber,
87
+ type: 'same-line',
88
+ ruleId: sameLineMatch[1] as VulnerabilityCategory | undefined,
89
+ reason: sameLineMatch[2].trim(),
90
+ commentText: line.substring(commentIndex).trim(),
91
+ }
92
+ }
93
+ }
94
+
95
+ // Block start pattern
96
+ const blockStartMatch = line.match(PATTERNS.blockStart)
97
+ if (blockStartMatch) {
98
+ return {
99
+ lineNumber,
100
+ type: 'block-start',
101
+ ruleId: blockStartMatch[1] as VulnerabilityCategory | undefined,
102
+ reason: blockStartMatch[2].trim(),
103
+ commentText: blockStartMatch[0],
104
+ }
105
+ }
106
+
107
+ // Block end pattern
108
+ const blockEndMatch = line.match(PATTERNS.blockEnd)
109
+ if (blockEndMatch) {
110
+ return {
111
+ lineNumber,
112
+ type: 'block-end',
113
+ reason: '', // Block end has no reason
114
+ commentText: blockEndMatch[0],
115
+ }
116
+ }
117
+
118
+ return null
119
+ }
120
+
121
+ /**
122
+ * Parse all inline suppressions from file content
123
+ *
124
+ * Returns a map of line numbers to their effective suppressions.
125
+ * A line can be suppressed by:
126
+ * 1. A same-line comment (oculum-ignore)
127
+ * 2. A next-line comment on the previous line (oculum-ignore-next-line)
128
+ * 3. Being within a block suppression (oculum-ignore-block ... oculum-ignore-block-end)
129
+ */
130
+ export function parseInlineSuppressions(content: string): Map<number, InlineSuppression> {
131
+ const lines = content.split('\n')
132
+ const suppressions = new Map<number, InlineSuppression>()
133
+
134
+ // Track block suppression state
135
+ let currentBlock: { startLine: number; reason: string; ruleId?: VulnerabilityCategory } | null = null
136
+
137
+ for (let i = 0; i < lines.length; i++) {
138
+ const lineNumber = i + 1 // 1-indexed
139
+ const line = lines[i]
140
+
141
+ const suppression = parseLineForSuppression(line, lineNumber)
142
+
143
+ if (suppression) {
144
+ switch (suppression.type) {
145
+ case 'next-line':
146
+ // Apply to the next line
147
+ if (i + 1 < lines.length) {
148
+ suppressions.set(lineNumber + 1, {
149
+ ...suppression,
150
+ lineNumber: lineNumber + 1,
151
+ })
152
+ }
153
+ break
154
+
155
+ case 'same-line':
156
+ // Apply to this line
157
+ suppressions.set(lineNumber, suppression)
158
+ break
159
+
160
+ case 'block-start':
161
+ // Start tracking block suppression
162
+ currentBlock = {
163
+ startLine: lineNumber,
164
+ reason: suppression.reason,
165
+ ruleId: suppression.ruleId,
166
+ }
167
+ break
168
+
169
+ case 'block-end':
170
+ // End block suppression
171
+ currentBlock = null
172
+ break
173
+ }
174
+ }
175
+
176
+ // If we're in a block suppression, add suppression for this line
177
+ // (unless it's the block start/end line itself)
178
+ if (currentBlock && suppression?.type !== 'block-start' && suppression?.type !== 'block-end') {
179
+ // Don't override explicit same-line suppressions
180
+ if (!suppressions.has(lineNumber)) {
181
+ suppressions.set(lineNumber, {
182
+ lineNumber,
183
+ type: 'block-start', // Mark as from block
184
+ reason: currentBlock.reason,
185
+ ruleId: currentBlock.ruleId,
186
+ commentText: `Block suppression from line ${currentBlock.startLine}`,
187
+ })
188
+ }
189
+ }
190
+ }
191
+
192
+ return suppressions
193
+ }
194
+
195
+ /**
196
+ * Check if a specific line is suppressed
197
+ */
198
+ export function isLineSuppressed(
199
+ suppressions: Map<number, InlineSuppression>,
200
+ lineNumber: number,
201
+ category?: VulnerabilityCategory
202
+ ): InlineSuppression | null {
203
+ const suppression = suppressions.get(lineNumber)
204
+
205
+ if (!suppression) {
206
+ return null
207
+ }
208
+
209
+ // If suppression specifies a rule and category doesn't match, not suppressed
210
+ if (suppression.ruleId && category && suppression.ruleId !== category) {
211
+ return null
212
+ }
213
+
214
+ return suppression
215
+ }
216
+
217
+ /**
218
+ * Extract all unique suppression reasons from a file
219
+ * Useful for auditing suppressed findings
220
+ */
221
+ export function extractSuppressionReasons(content: string): Array<{
222
+ reason: string
223
+ ruleId?: VulnerabilityCategory
224
+ lineNumber: number
225
+ type: InlineSuppression['type']
226
+ }> {
227
+ const suppressions = parseInlineSuppressions(content)
228
+ const seen = new Set<string>()
229
+ const results: Array<{
230
+ reason: string
231
+ ruleId?: VulnerabilityCategory
232
+ lineNumber: number
233
+ type: InlineSuppression['type']
234
+ }> = []
235
+
236
+ for (const [lineNumber, suppression] of suppressions) {
237
+ // Only include unique comment sources (not duplicates from block propagation)
238
+ const key = `${suppression.commentText}`
239
+ if (!seen.has(key) && suppression.type !== 'block-end') {
240
+ seen.add(key)
241
+ results.push({
242
+ reason: suppression.reason,
243
+ ruleId: suppression.ruleId,
244
+ lineNumber,
245
+ type: suppression.type,
246
+ })
247
+ }
248
+ }
249
+
250
+ return results
251
+ }
252
+
253
+ /**
254
+ * Generate a suppression comment for a given line
255
+ */
256
+ export function generateSuppressionComment(
257
+ reason: string,
258
+ options: {
259
+ type?: 'next-line' | 'same-line'
260
+ ruleId?: VulnerabilityCategory
261
+ commentStyle?: '//' | '#' | '--'
262
+ } = {}
263
+ ): string {
264
+ const { type = 'next-line', ruleId, commentStyle = '//' } = options
265
+
266
+ const ruleSpec = ruleId ? ` [${ruleId}]` : ''
267
+
268
+ if (type === 'next-line') {
269
+ return `${commentStyle} oculum-ignore-next-line${ruleSpec}: ${reason}`
270
+ }
271
+
272
+ return `${commentStyle} oculum-ignore${ruleSpec}: ${reason}`
273
+ }
@@ -0,0 +1,379 @@
1
+ /**
2
+ * Suppression Manager
3
+ * Central class for managing all suppression logic
4
+ */
5
+
6
+ import { minimatch } from 'minimatch'
7
+ import type { Vulnerability, ScanFile } from '../types'
8
+ import type {
9
+ SuppressionConfig,
10
+ SuppressionMatch,
11
+ SuppressionResult,
12
+ SuppressedVulnerability,
13
+ InlineSuppression,
14
+ RuleSuppression,
15
+ FindingSuppression,
16
+ } from './types'
17
+ import { loadSuppressionConfig, isExpired } from './config-loader'
18
+ import { parseInlineSuppressions, isLineSuppressed } from './inline-parser'
19
+ import { computeFindingHash, normalizePathForHash } from './hash'
20
+
21
+ /**
22
+ * Options for SuppressionManager
23
+ */
24
+ export interface SuppressionManagerOptions {
25
+ /** Path to the project root (for finding config files) */
26
+ projectPath: string
27
+ /** Optional pre-loaded config (skips file loading) */
28
+ config?: SuppressionConfig
29
+ /** Whether to include expired suppressions (for auditing) */
30
+ includeExpired?: boolean
31
+ }
32
+
33
+ /**
34
+ * SuppressionManager handles all suppression logic
35
+ *
36
+ * Priority order for suppressions:
37
+ * 1. Inline comment suppressions (highest priority)
38
+ * 2. Config file finding suppressions (by hash)
39
+ * 3. Config file rule suppressions (by category)
40
+ */
41
+ export class SuppressionManager {
42
+ private config: SuppressionConfig
43
+ private configPath: string | undefined
44
+ private configErrors: string[]
45
+ private includeExpired: boolean
46
+ private projectPath: string
47
+
48
+ // Cache for inline suppressions by file path
49
+ private inlineSuppressionCache: Map<string, Map<number, InlineSuppression>> = new Map()
50
+
51
+ constructor(options: SuppressionManagerOptions) {
52
+ this.projectPath = options.projectPath
53
+ this.includeExpired = options.includeExpired ?? false
54
+
55
+ if (options.config) {
56
+ this.config = options.config
57
+ this.configPath = undefined
58
+ this.configErrors = []
59
+ } else {
60
+ const result = loadSuppressionConfig(options.projectPath)
61
+ this.config = result.config
62
+ this.configPath = result.configPath
63
+ this.configErrors = result.errors
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Get configuration errors (if any)
69
+ */
70
+ getConfigErrors(): string[] {
71
+ return this.configErrors
72
+ }
73
+
74
+ /**
75
+ * Get the path to the config file (if found)
76
+ */
77
+ getConfigPath(): string | undefined {
78
+ return this.configPath
79
+ }
80
+
81
+ /**
82
+ * Check if a file path should be ignored entirely
83
+ */
84
+ isPathIgnored(filePath: string): boolean {
85
+ if (!this.config.ignore || this.config.ignore.length === 0) {
86
+ return false
87
+ }
88
+
89
+ const normalizedPath = normalizePathForHash(filePath)
90
+
91
+ return this.config.ignore.some(pattern =>
92
+ minimatch(normalizedPath, pattern, { dot: true })
93
+ )
94
+ }
95
+
96
+ /**
97
+ * Parse inline suppressions for a file
98
+ */
99
+ private getInlineSuppressions(
100
+ filePath: string,
101
+ content: string
102
+ ): Map<number, InlineSuppression> {
103
+ // Check cache first
104
+ const cached = this.inlineSuppressionCache.get(filePath)
105
+ if (cached) {
106
+ return cached
107
+ }
108
+
109
+ // Parse and cache
110
+ const suppressions = parseInlineSuppressions(content)
111
+ this.inlineSuppressionCache.set(filePath, suppressions)
112
+ return suppressions
113
+ }
114
+
115
+ /**
116
+ * Check if a finding is suppressed
117
+ *
118
+ * Priority:
119
+ * 1. Inline comment (highest)
120
+ * 2. Config finding suppression (by hash)
121
+ * 3. Config rule suppression (by category)
122
+ */
123
+ isFindingSuppressed(
124
+ finding: Vulnerability,
125
+ fileContent?: string
126
+ ): SuppressionMatch {
127
+ const hash = computeFindingHash(finding)
128
+ const normalizedPath = normalizePathForHash(finding.filePath)
129
+
130
+ // 1. Check inline suppressions (if file content provided)
131
+ if (fileContent) {
132
+ const inlineSuppressions = this.getInlineSuppressions(finding.filePath, fileContent)
133
+ const inlineSuppression = isLineSuppressed(
134
+ inlineSuppressions,
135
+ finding.lineNumber,
136
+ finding.category
137
+ )
138
+
139
+ if (inlineSuppression) {
140
+ return {
141
+ suppressed: true,
142
+ match: {
143
+ type: 'inline',
144
+ reason: inlineSuppression.reason,
145
+ },
146
+ hash,
147
+ }
148
+ }
149
+ }
150
+
151
+ // 2. Check config finding suppressions (by hash)
152
+ const findingSuppressions = this.config.suppressions?.findings || []
153
+ const findingSuppression = findingSuppressions.find(s => s.hash === hash)
154
+
155
+ if (findingSuppression) {
156
+ const expired = isExpired(findingSuppression.expires)
157
+
158
+ if (expired && !this.includeExpired) {
159
+ // Expired - not suppressed
160
+ return {
161
+ suppressed: false,
162
+ match: {
163
+ type: 'config-finding',
164
+ reason: findingSuppression.reason,
165
+ expires: findingSuppression.expires,
166
+ expired: true,
167
+ },
168
+ hash,
169
+ }
170
+ }
171
+
172
+ return {
173
+ suppressed: true,
174
+ match: {
175
+ type: 'config-finding',
176
+ reason: findingSuppression.reason,
177
+ expires: findingSuppression.expires,
178
+ expired,
179
+ },
180
+ hash,
181
+ }
182
+ }
183
+
184
+ // 3. Check config rule suppressions (by category)
185
+ const ruleSuppressions = this.config.suppressions?.rules || []
186
+ const ruleSuppression = ruleSuppressions.find(s => {
187
+ // Must match category
188
+ if (s.category !== finding.category) {
189
+ return false
190
+ }
191
+
192
+ // If paths specified, must match one of them
193
+ if (s.paths && s.paths.length > 0) {
194
+ const matchesPath = s.paths.some(pattern =>
195
+ minimatch(normalizedPath, pattern, { dot: true })
196
+ )
197
+ if (!matchesPath) {
198
+ return false
199
+ }
200
+ }
201
+
202
+ return true
203
+ })
204
+
205
+ if (ruleSuppression) {
206
+ const expired = isExpired(ruleSuppression.expires)
207
+
208
+ if (expired && !this.includeExpired) {
209
+ // Expired - not suppressed
210
+ return {
211
+ suppressed: false,
212
+ match: {
213
+ type: 'config-rule',
214
+ reason: ruleSuppression.reason,
215
+ expires: ruleSuppression.expires,
216
+ expired: true,
217
+ },
218
+ hash,
219
+ }
220
+ }
221
+
222
+ return {
223
+ suppressed: true,
224
+ match: {
225
+ type: 'config-rule',
226
+ reason: ruleSuppression.reason,
227
+ expires: ruleSuppression.expires,
228
+ expired,
229
+ },
230
+ hash,
231
+ }
232
+ }
233
+
234
+ // Not suppressed
235
+ return {
236
+ suppressed: false,
237
+ hash,
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Apply suppressions to a list of findings
243
+ */
244
+ applySuppressions(
245
+ findings: Vulnerability[],
246
+ files: ScanFile[]
247
+ ): SuppressionResult {
248
+ // Build file content lookup
249
+ const fileContentMap = new Map<string, string>()
250
+ for (const file of files) {
251
+ fileContentMap.set(normalizePathForHash(file.path), file.content)
252
+ }
253
+
254
+ const passed: Vulnerability[] = []
255
+ const suppressed: SuppressedVulnerability[] = []
256
+ let expiredCount = 0
257
+
258
+ const stats = {
259
+ total: findings.length,
260
+ inlineSuppressed: 0,
261
+ configFindingSuppressed: 0,
262
+ configRuleSuppressed: 0,
263
+ expired: 0,
264
+ }
265
+
266
+ for (const finding of findings) {
267
+ // Get file content for inline suppression checking
268
+ const normalizedPath = normalizePathForHash(finding.filePath)
269
+ const fileContent = fileContentMap.get(normalizedPath)
270
+
271
+ const result = this.isFindingSuppressed(finding, fileContent)
272
+
273
+ if (result.suppressed) {
274
+ suppressed.push({
275
+ vulnerability: {
276
+ id: finding.id,
277
+ filePath: finding.filePath,
278
+ lineNumber: finding.lineNumber,
279
+ category: finding.category,
280
+ severity: finding.severity,
281
+ title: finding.title,
282
+ },
283
+ suppression: {
284
+ type: result.match!.type,
285
+ reason: result.match!.reason,
286
+ expires: result.match!.expires,
287
+ hash: result.hash,
288
+ },
289
+ })
290
+
291
+ // Update stats
292
+ switch (result.match!.type) {
293
+ case 'inline':
294
+ stats.inlineSuppressed++
295
+ break
296
+ case 'config-finding':
297
+ stats.configFindingSuppressed++
298
+ break
299
+ case 'config-rule':
300
+ stats.configRuleSuppressed++
301
+ break
302
+ }
303
+ } else {
304
+ passed.push(finding)
305
+
306
+ // Track expired suppressions
307
+ if (result.match?.expired) {
308
+ expiredCount++
309
+ stats.expired++
310
+ }
311
+ }
312
+ }
313
+
314
+ return {
315
+ findings: passed,
316
+ suppressed,
317
+ expiredSuppressions: expiredCount,
318
+ stats,
319
+ }
320
+ }
321
+
322
+ /**
323
+ * Get all suppressions from config
324
+ */
325
+ getAllSuppressions(): {
326
+ rules: RuleSuppression[]
327
+ findings: FindingSuppression[]
328
+ } {
329
+ return {
330
+ rules: this.config.suppressions?.rules || [],
331
+ findings: this.config.suppressions?.findings || [],
332
+ }
333
+ }
334
+
335
+ /**
336
+ * Get ignore patterns from config
337
+ */
338
+ getIgnorePatterns(): string[] {
339
+ return this.config.ignore || []
340
+ }
341
+
342
+ /**
343
+ * Check if suppression system is active (has any suppressions)
344
+ */
345
+ hasSuppressions(): boolean {
346
+ const rules = this.config.suppressions?.rules || []
347
+ const findings = this.config.suppressions?.findings || []
348
+ const ignore = this.config.ignore || []
349
+
350
+ return rules.length > 0 || findings.length > 0 || ignore.length > 0
351
+ }
352
+
353
+ /**
354
+ * Get summary of suppression configuration
355
+ */
356
+ getSummary(): {
357
+ configPath: string | undefined
358
+ ruleCount: number
359
+ findingCount: number
360
+ ignorePatternCount: number
361
+ hasErrors: boolean
362
+ } {
363
+ return {
364
+ configPath: this.configPath,
365
+ ruleCount: this.config.suppressions?.rules?.length || 0,
366
+ findingCount: this.config.suppressions?.findings?.length || 0,
367
+ ignorePatternCount: this.config.ignore?.length || 0,
368
+ hasErrors: this.configErrors.length > 0,
369
+ }
370
+ }
371
+
372
+ /**
373
+ * Clear the inline suppression cache
374
+ * (useful if files have been modified)
375
+ */
376
+ clearCache(): void {
377
+ this.inlineSuppressionCache.clear()
378
+ }
379
+ }