@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,212 @@
1
+ /**
2
+ * Inline Suppression Parser Tests
3
+ */
4
+
5
+ import {
6
+ parseInlineSuppressions,
7
+ isLineSuppressed,
8
+ extractSuppressionReasons,
9
+ generateSuppressionComment,
10
+ } from '../inline-parser'
11
+
12
+ describe('Inline Suppression Parser', () => {
13
+ describe('parseInlineSuppressions', () => {
14
+ it('should parse next-line suppression comments', () => {
15
+ const content = `
16
+ const x = 1;
17
+ // oculum-ignore-next-line: false positive
18
+ const secret = "abc123";
19
+ const y = 2;
20
+ `
21
+ const suppressions = parseInlineSuppressions(content)
22
+
23
+ // Line 4 (const secret) should be suppressed by line 3's comment
24
+ expect(suppressions.has(4)).toBe(true)
25
+ expect(suppressions.get(4)?.reason).toBe('false positive')
26
+ expect(suppressions.get(4)?.type).toBe('next-line')
27
+ })
28
+
29
+ it('should parse same-line suppression comments', () => {
30
+ const content = `
31
+ const x = 1;
32
+ const secret = "abc123"; // oculum-ignore: known safe
33
+ const y = 2;
34
+ `
35
+ const suppressions = parseInlineSuppressions(content)
36
+
37
+ // Line 3 should be suppressed
38
+ expect(suppressions.has(3)).toBe(true)
39
+ expect(suppressions.get(3)?.reason).toBe('known safe')
40
+ expect(suppressions.get(3)?.type).toBe('same-line')
41
+ })
42
+
43
+ it('should parse block suppressions', () => {
44
+ const content = `
45
+ const x = 1;
46
+ /* oculum-ignore-block: legacy code */
47
+ const secret1 = "abc";
48
+ const secret2 = "def";
49
+ /* oculum-ignore-block-end */
50
+ const y = 2;
51
+ `
52
+ const suppressions = parseInlineSuppressions(content)
53
+
54
+ // Lines 4 and 5 should be suppressed by the block
55
+ expect(suppressions.has(4)).toBe(true)
56
+ expect(suppressions.has(5)).toBe(true)
57
+ expect(suppressions.get(4)?.reason).toBe('legacy code')
58
+ expect(suppressions.get(5)?.reason).toBe('legacy code')
59
+
60
+ // Lines before and after block should not be suppressed
61
+ expect(suppressions.has(2)).toBe(false)
62
+ expect(suppressions.has(7)).toBe(false)
63
+ })
64
+
65
+ it('should parse rule-specific suppressions', () => {
66
+ const content = `
67
+ // oculum-ignore-next-line [hardcoded_secret]: test data
68
+ const secret = "test123";
69
+ `
70
+ const suppressions = parseInlineSuppressions(content)
71
+
72
+ expect(suppressions.has(3)).toBe(true)
73
+ expect(suppressions.get(3)?.ruleId).toBe('hardcoded_secret')
74
+ expect(suppressions.get(3)?.reason).toBe('test data')
75
+ })
76
+
77
+ it('should support Python-style comments (#)', () => {
78
+ const content = `
79
+ x = 1
80
+ # oculum-ignore-next-line: test data
81
+ secret = "abc123"
82
+ `
83
+ const suppressions = parseInlineSuppressions(content)
84
+
85
+ expect(suppressions.has(4)).toBe(true)
86
+ expect(suppressions.get(4)?.reason).toBe('test data')
87
+ })
88
+
89
+ it('should support SQL-style comments (--)', () => {
90
+ const content = `
91
+ SELECT 1;
92
+ -- oculum-ignore-next-line: intentional
93
+ SELECT password FROM users;
94
+ `
95
+ const suppressions = parseInlineSuppressions(content)
96
+
97
+ expect(suppressions.has(4)).toBe(true)
98
+ expect(suppressions.get(4)?.reason).toBe('intentional')
99
+ })
100
+
101
+ it('should handle empty files', () => {
102
+ const suppressions = parseInlineSuppressions('')
103
+ expect(suppressions.size).toBe(0)
104
+ })
105
+
106
+ it('should not match partial oculum-ignore patterns', () => {
107
+ const content = `
108
+ // This is not oculum-ignore but mentions it
109
+ const x = "oculum-ignore";
110
+ `
111
+ const suppressions = parseInlineSuppressions(content)
112
+ expect(suppressions.size).toBe(0)
113
+ })
114
+ })
115
+
116
+ describe('isLineSuppressed', () => {
117
+ it('should return suppression for suppressed line', () => {
118
+ const content = `
119
+ // oculum-ignore-next-line: reason
120
+ const x = 1;
121
+ `
122
+ const suppressions = parseInlineSuppressions(content)
123
+ const result = isLineSuppressed(suppressions, 3)
124
+
125
+ expect(result).not.toBeNull()
126
+ expect(result?.reason).toBe('reason')
127
+ })
128
+
129
+ it('should return null for non-suppressed line', () => {
130
+ const content = `
131
+ // oculum-ignore-next-line: reason
132
+ const x = 1;
133
+ const y = 2;
134
+ `
135
+ const suppressions = parseInlineSuppressions(content)
136
+ const result = isLineSuppressed(suppressions, 4)
137
+
138
+ expect(result).toBeNull()
139
+ })
140
+
141
+ it('should respect rule-specific suppressions', () => {
142
+ const content = `
143
+ // oculum-ignore-next-line [hardcoded_secret]: reason
144
+ const x = 1;
145
+ `
146
+ const suppressions = parseInlineSuppressions(content)
147
+
148
+ // Should match hardcoded_secret
149
+ expect(isLineSuppressed(suppressions, 3, 'hardcoded_secret')).not.toBeNull()
150
+
151
+ // Should not match other categories
152
+ expect(isLineSuppressed(suppressions, 3, 'high_entropy_string')).toBeNull()
153
+
154
+ // Should match if no category specified
155
+ expect(isLineSuppressed(suppressions, 3)).not.toBeNull()
156
+ })
157
+ })
158
+
159
+ describe('extractSuppressionReasons', () => {
160
+ it('should extract all unique suppression reasons', () => {
161
+ const content = `
162
+ // oculum-ignore-next-line: reason 1
163
+ const a = 1;
164
+ // oculum-ignore-next-line: reason 2
165
+ const b = 2;
166
+ `
167
+ const reasons = extractSuppressionReasons(content)
168
+
169
+ expect(reasons).toHaveLength(2)
170
+ expect(reasons.map(r => r.reason)).toContain('reason 1')
171
+ expect(reasons.map(r => r.reason)).toContain('reason 2')
172
+ })
173
+
174
+ it('should not duplicate block suppression reasons', () => {
175
+ const content = `
176
+ /* oculum-ignore-block: block reason */
177
+ const a = 1;
178
+ const b = 2;
179
+ /* oculum-ignore-block-end */
180
+ `
181
+ const reasons = extractSuppressionReasons(content)
182
+
183
+ // Should only have one entry for the block, not one per line
184
+ expect(reasons).toHaveLength(1)
185
+ expect(reasons[0].reason).toBe('block reason')
186
+ })
187
+ })
188
+
189
+ describe('generateSuppressionComment', () => {
190
+ it('should generate next-line comment', () => {
191
+ const comment = generateSuppressionComment('false positive')
192
+ expect(comment).toBe('// oculum-ignore-next-line: false positive')
193
+ })
194
+
195
+ it('should generate same-line comment', () => {
196
+ const comment = generateSuppressionComment('known safe', { type: 'same-line' })
197
+ expect(comment).toBe('// oculum-ignore: known safe')
198
+ })
199
+
200
+ it('should generate comment with rule ID', () => {
201
+ const comment = generateSuppressionComment('test data', { ruleId: 'hardcoded_secret' })
202
+ expect(comment).toBe('// oculum-ignore-next-line [hardcoded_secret]: test data')
203
+ })
204
+
205
+ it('should support different comment styles', () => {
206
+ expect(generateSuppressionComment('reason', { commentStyle: '#' }))
207
+ .toBe('# oculum-ignore-next-line: reason')
208
+ expect(generateSuppressionComment('reason', { commentStyle: '--' }))
209
+ .toBe('-- oculum-ignore-next-line: reason')
210
+ })
211
+ })
212
+ })
@@ -0,0 +1,415 @@
1
+ /**
2
+ * SuppressionManager Tests
3
+ */
4
+
5
+ import { SuppressionManager } from '../manager'
6
+ import type { SuppressionConfig } from '../types'
7
+ import type { Vulnerability, ScanFile } from '../../types'
8
+
9
+ describe('SuppressionManager', () => {
10
+ // Helper to create a mock vulnerability
11
+ function createVulnerability(overrides: Partial<Vulnerability> = {}): Vulnerability {
12
+ return {
13
+ id: 'test-1',
14
+ filePath: 'src/index.ts',
15
+ lineNumber: 10,
16
+ lineContent: 'const secret = "api_key_12345"',
17
+ severity: 'high',
18
+ category: 'hardcoded_secret',
19
+ title: 'Hardcoded Secret',
20
+ description: 'Found a hardcoded secret',
21
+ confidence: 'high',
22
+ layer: 1,
23
+ ...overrides,
24
+ }
25
+ }
26
+
27
+ // Helper to create a mock scan file
28
+ function createScanFile(overrides: Partial<ScanFile> = {}): ScanFile {
29
+ return {
30
+ path: 'src/index.ts',
31
+ content: 'const secret = "api_key_12345";',
32
+ language: 'typescript',
33
+ size: 100,
34
+ ...overrides,
35
+ }
36
+ }
37
+
38
+ describe('constructor', () => {
39
+ it('should accept a pre-loaded config', () => {
40
+ const config: SuppressionConfig = {
41
+ version: 1,
42
+ suppressions: {
43
+ rules: [{
44
+ category: 'hardcoded_secret',
45
+ reason: 'test',
46
+ }],
47
+ },
48
+ }
49
+
50
+ const manager = new SuppressionManager({
51
+ projectPath: '/fake/path',
52
+ config,
53
+ })
54
+
55
+ expect(manager.hasSuppressions()).toBe(true)
56
+ expect(manager.getAllSuppressions().rules).toHaveLength(1)
57
+ })
58
+
59
+ it('should return default config when path has no config file', () => {
60
+ const manager = new SuppressionManager({
61
+ projectPath: '/nonexistent/path',
62
+ })
63
+
64
+ expect(manager.hasSuppressions()).toBe(false)
65
+ expect(manager.getConfigPath()).toBeUndefined()
66
+ })
67
+ })
68
+
69
+ describe('isPathIgnored', () => {
70
+ it('should match simple patterns', () => {
71
+ const config: SuppressionConfig = {
72
+ version: 1,
73
+ ignore: ['**/*.test.ts', 'node_modules/**'],
74
+ }
75
+
76
+ const manager = new SuppressionManager({
77
+ projectPath: '/fake',
78
+ config,
79
+ })
80
+
81
+ expect(manager.isPathIgnored('src/utils.test.ts')).toBe(true)
82
+ expect(manager.isPathIgnored('node_modules/lodash/index.js')).toBe(true)
83
+ expect(manager.isPathIgnored('src/index.ts')).toBe(false)
84
+ })
85
+
86
+ it('should return false when no ignore patterns', () => {
87
+ const manager = new SuppressionManager({
88
+ projectPath: '/fake',
89
+ config: { version: 1 },
90
+ })
91
+
92
+ expect(manager.isPathIgnored('anything.ts')).toBe(false)
93
+ })
94
+ })
95
+
96
+ describe('isFindingSuppressed', () => {
97
+ it('should suppress by inline comment', () => {
98
+ const config: SuppressionConfig = { version: 1 }
99
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
100
+
101
+ // Line 4 should be suppressed (line 3 has the comment)
102
+ const finding = createVulnerability({ lineNumber: 4 })
103
+ const fileContent = `const x = 1;
104
+ const y = 2;
105
+ // oculum-ignore-next-line: false positive
106
+ const secret = "api_key_12345";
107
+ `
108
+
109
+ const result = manager.isFindingSuppressed(finding, fileContent)
110
+
111
+ expect(result.suppressed).toBe(true)
112
+ expect(result.match?.type).toBe('inline')
113
+ expect(result.match?.reason).toBe('false positive')
114
+ })
115
+
116
+ it('should suppress by config finding hash', () => {
117
+ const finding = createVulnerability()
118
+ // First get the hash
119
+ const tempManager = new SuppressionManager({
120
+ projectPath: '/fake',
121
+ config: { version: 1 },
122
+ })
123
+ const { hash } = tempManager.isFindingSuppressed(finding)
124
+
125
+ // Now create a manager with that hash suppressed
126
+ const config: SuppressionConfig = {
127
+ version: 1,
128
+ suppressions: {
129
+ findings: [{
130
+ hash,
131
+ file: 'src/index.ts',
132
+ reason: 'suppressed by hash',
133
+ }],
134
+ },
135
+ }
136
+
137
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
138
+ const result = manager.isFindingSuppressed(finding)
139
+
140
+ expect(result.suppressed).toBe(true)
141
+ expect(result.match?.type).toBe('config-finding')
142
+ expect(result.match?.reason).toBe('suppressed by hash')
143
+ })
144
+
145
+ it('should suppress by config rule', () => {
146
+ const config: SuppressionConfig = {
147
+ version: 1,
148
+ suppressions: {
149
+ rules: [{
150
+ category: 'hardcoded_secret',
151
+ reason: 'all hardcoded secrets suppressed',
152
+ }],
153
+ },
154
+ }
155
+
156
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
157
+ const finding = createVulnerability()
158
+
159
+ const result = manager.isFindingSuppressed(finding)
160
+
161
+ expect(result.suppressed).toBe(true)
162
+ expect(result.match?.type).toBe('config-rule')
163
+ expect(result.match?.reason).toBe('all hardcoded secrets suppressed')
164
+ })
165
+
166
+ it('should respect rule path restrictions', () => {
167
+ const config: SuppressionConfig = {
168
+ version: 1,
169
+ suppressions: {
170
+ rules: [{
171
+ category: 'hardcoded_secret',
172
+ reason: 'only in tests',
173
+ paths: ['**/*.test.ts'],
174
+ }],
175
+ },
176
+ }
177
+
178
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
179
+
180
+ const testFinding = createVulnerability({ filePath: 'src/utils.test.ts' })
181
+ const srcFinding = createVulnerability({ filePath: 'src/index.ts' })
182
+
183
+ expect(manager.isFindingSuppressed(testFinding).suppressed).toBe(true)
184
+ expect(manager.isFindingSuppressed(srcFinding).suppressed).toBe(false)
185
+ })
186
+
187
+ it('should handle expired suppressions', () => {
188
+ const finding = createVulnerability()
189
+ const tempManager = new SuppressionManager({
190
+ projectPath: '/fake',
191
+ config: { version: 1 },
192
+ })
193
+ const { hash } = tempManager.isFindingSuppressed(finding)
194
+
195
+ const config: SuppressionConfig = {
196
+ version: 1,
197
+ suppressions: {
198
+ findings: [{
199
+ hash,
200
+ file: 'src/index.ts',
201
+ reason: 'expired suppression',
202
+ expires: '2020-01-01', // Already expired
203
+ }],
204
+ },
205
+ }
206
+
207
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
208
+ const result = manager.isFindingSuppressed(finding)
209
+
210
+ expect(result.suppressed).toBe(false)
211
+ expect(result.match?.expired).toBe(true)
212
+ })
213
+
214
+ it('should prioritize inline over config', () => {
215
+ // Line 4 is the secret line, line 3 has the comment
216
+ const finding = createVulnerability({ lineNumber: 4 })
217
+ const tempManager = new SuppressionManager({
218
+ projectPath: '/fake',
219
+ config: { version: 1 },
220
+ })
221
+ const { hash } = tempManager.isFindingSuppressed(finding)
222
+
223
+ const config: SuppressionConfig = {
224
+ version: 1,
225
+ suppressions: {
226
+ findings: [{
227
+ hash,
228
+ file: 'src/index.ts',
229
+ reason: 'config reason',
230
+ }],
231
+ },
232
+ }
233
+
234
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
235
+ const fileContent = `const x = 1;
236
+ const y = 2;
237
+ // oculum-ignore-next-line: inline reason
238
+ const secret = "api_key_12345";
239
+ `
240
+
241
+ const result = manager.isFindingSuppressed(finding, fileContent)
242
+
243
+ // Should be suppressed by inline (higher priority)
244
+ expect(result.suppressed).toBe(true)
245
+ expect(result.match?.type).toBe('inline')
246
+ expect(result.match?.reason).toBe('inline reason')
247
+ })
248
+ })
249
+
250
+ describe('applySuppressions', () => {
251
+ it('should filter suppressed findings', () => {
252
+ const config: SuppressionConfig = {
253
+ version: 1,
254
+ suppressions: {
255
+ rules: [{
256
+ category: 'hardcoded_secret',
257
+ reason: 'suppress all secrets',
258
+ }],
259
+ },
260
+ }
261
+
262
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
263
+
264
+ const findings = [
265
+ createVulnerability({ id: '1', category: 'hardcoded_secret' }),
266
+ createVulnerability({ id: '2', category: 'high_entropy_string' }),
267
+ createVulnerability({ id: '3', category: 'hardcoded_secret' }),
268
+ ]
269
+
270
+ const files = [createScanFile()]
271
+
272
+ const result = manager.applySuppressions(findings, files)
273
+
274
+ expect(result.findings).toHaveLength(1)
275
+ expect(result.findings[0].category).toBe('high_entropy_string')
276
+
277
+ expect(result.suppressed).toHaveLength(2)
278
+ expect(result.stats.configRuleSuppressed).toBe(2)
279
+ })
280
+
281
+ it('should include suppression details in result', () => {
282
+ const config: SuppressionConfig = {
283
+ version: 1,
284
+ suppressions: {
285
+ rules: [{
286
+ category: 'hardcoded_secret',
287
+ reason: 'test reason',
288
+ }],
289
+ },
290
+ }
291
+
292
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
293
+ const finding = createVulnerability()
294
+ const files = [createScanFile()]
295
+
296
+ const result = manager.applySuppressions([finding], files)
297
+
298
+ expect(result.suppressed).toHaveLength(1)
299
+ expect(result.suppressed[0].suppression.reason).toBe('test reason')
300
+ expect(result.suppressed[0].suppression.type).toBe('config-rule')
301
+ expect(result.suppressed[0].vulnerability.filePath).toBe(finding.filePath)
302
+ })
303
+
304
+ it('should track expired suppressions count', () => {
305
+ const finding = createVulnerability()
306
+ const tempManager = new SuppressionManager({
307
+ projectPath: '/fake',
308
+ config: { version: 1 },
309
+ })
310
+ const { hash } = tempManager.isFindingSuppressed(finding)
311
+
312
+ const config: SuppressionConfig = {
313
+ version: 1,
314
+ suppressions: {
315
+ findings: [{
316
+ hash,
317
+ file: 'src/index.ts',
318
+ reason: 'expired',
319
+ expires: '2020-01-01',
320
+ }],
321
+ },
322
+ }
323
+
324
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
325
+ const files = [createScanFile()]
326
+
327
+ const result = manager.applySuppressions([finding], files)
328
+
329
+ expect(result.findings).toHaveLength(1) // Not suppressed because expired
330
+ expect(result.expiredSuppressions).toBe(1)
331
+ expect(result.stats.expired).toBe(1)
332
+ })
333
+
334
+ it('should correctly count by suppression type', () => {
335
+ const config: SuppressionConfig = {
336
+ version: 1,
337
+ suppressions: {
338
+ rules: [{
339
+ category: 'hardcoded_secret',
340
+ reason: 'rule suppression',
341
+ }],
342
+ },
343
+ }
344
+
345
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
346
+
347
+ // Create a finding that will be suppressed by inline (line 4 - after the comment on line 3)
348
+ const inlineFinding = createVulnerability({
349
+ id: '1',
350
+ category: 'high_entropy_string',
351
+ lineNumber: 4,
352
+ lineContent: 'const entropy = "abcdef123456";',
353
+ })
354
+
355
+ // Create a finding that will be suppressed by rule (line 5)
356
+ const ruleFinding = createVulnerability({
357
+ id: '2',
358
+ category: 'hardcoded_secret',
359
+ lineNumber: 5,
360
+ lineContent: 'const secret = "api_key";',
361
+ })
362
+
363
+ // Create a finding that won't be suppressed (line 6)
364
+ const passedFinding = createVulnerability({
365
+ id: '3',
366
+ category: 'sql_injection',
367
+ lineNumber: 6,
368
+ lineContent: 'const query = "SELECT * FROM users";',
369
+ })
370
+
371
+ const findings = [inlineFinding, ruleFinding, passedFinding]
372
+
373
+ const fileContent = `const x = 1;
374
+ const y = 2;
375
+ // oculum-ignore-next-line: inline reason
376
+ const entropy = "abcdef123456";
377
+ const secret = "api_key";
378
+ const query = "SELECT * FROM users";
379
+ `
380
+ const files = [createScanFile({ content: fileContent })]
381
+
382
+ const result = manager.applySuppressions(findings, files)
383
+
384
+ expect(result.findings).toHaveLength(1)
385
+ expect(result.stats.inlineSuppressed).toBe(1)
386
+ expect(result.stats.configRuleSuppressed).toBe(1)
387
+ })
388
+ })
389
+
390
+ describe('getSummary', () => {
391
+ it('should return accurate summary', () => {
392
+ const config: SuppressionConfig = {
393
+ version: 1,
394
+ suppressions: {
395
+ rules: [
396
+ { category: 'hardcoded_secret', reason: 'r1' },
397
+ { category: 'high_entropy_string', reason: 'r2' },
398
+ ],
399
+ findings: [
400
+ { hash: 'abc123def4567890', file: 'a.ts', reason: 'f1' },
401
+ ],
402
+ },
403
+ ignore: ['*.test.ts', '*.spec.ts'],
404
+ }
405
+
406
+ const manager = new SuppressionManager({ projectPath: '/fake', config })
407
+ const summary = manager.getSummary()
408
+
409
+ expect(summary.ruleCount).toBe(2)
410
+ expect(summary.findingCount).toBe(1)
411
+ expect(summary.ignorePatternCount).toBe(2)
412
+ expect(summary.hasErrors).toBe(false)
413
+ })
414
+ })
415
+ })