@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,382 @@
1
+ /**
2
+ * Config Loader Tests
3
+ */
4
+
5
+ import { writeFileSync, mkdirSync, rmSync } from 'fs'
6
+ import { join } from 'path'
7
+ import { tmpdir } from 'os'
8
+ import {
9
+ loadSuppressionConfig,
10
+ loadConfigFromPath,
11
+ findConfigFile,
12
+ writeSuppressionConfig,
13
+ addFindingSuppression,
14
+ removeFindingSuppression,
15
+ listSuppressions,
16
+ isExpired,
17
+ } from '../config-loader'
18
+ import type { SuppressionConfig, FindingSuppression } from '../types'
19
+
20
+ describe('Config Loader', () => {
21
+ let testDir: string
22
+
23
+ beforeEach(() => {
24
+ // Create unique temp directory for each test
25
+ testDir = join(tmpdir(), `oculum-test-${Date.now()}-${Math.random().toString(36).slice(2)}`)
26
+ mkdirSync(testDir, { recursive: true })
27
+ })
28
+
29
+ afterEach(() => {
30
+ // Clean up
31
+ try {
32
+ rmSync(testDir, { recursive: true, force: true })
33
+ } catch {
34
+ // Ignore cleanup errors
35
+ }
36
+ })
37
+
38
+ describe('findConfigFile', () => {
39
+ it('should find .oculum.yaml in current directory', () => {
40
+ writeFileSync(join(testDir, '.oculum.yaml'), 'version: 1')
41
+ const result = findConfigFile(testDir)
42
+ expect(result).toBe(join(testDir, '.oculum.yaml'))
43
+ })
44
+
45
+ it('should find .oculum.yml in current directory', () => {
46
+ writeFileSync(join(testDir, '.oculum.yml'), 'version: 1')
47
+ const result = findConfigFile(testDir)
48
+ expect(result).toBe(join(testDir, '.oculum.yml'))
49
+ })
50
+
51
+ it('should find oculum.config.json in current directory', () => {
52
+ writeFileSync(join(testDir, 'oculum.config.json'), '{"version": 1}')
53
+ const result = findConfigFile(testDir)
54
+ expect(result).toBe(join(testDir, 'oculum.config.json'))
55
+ })
56
+
57
+ it('should prefer .oculum.yaml over other formats', () => {
58
+ writeFileSync(join(testDir, '.oculum.yaml'), 'version: 1')
59
+ writeFileSync(join(testDir, 'oculum.config.json'), '{"version": 1}')
60
+ const result = findConfigFile(testDir)
61
+ expect(result).toBe(join(testDir, '.oculum.yaml'))
62
+ })
63
+
64
+ it('should find config in parent directory', () => {
65
+ const subDir = join(testDir, 'src', 'utils')
66
+ mkdirSync(subDir, { recursive: true })
67
+ writeFileSync(join(testDir, '.oculum.yaml'), 'version: 1')
68
+
69
+ const result = findConfigFile(subDir)
70
+ expect(result).toBe(join(testDir, '.oculum.yaml'))
71
+ })
72
+
73
+ it('should return null if no config found', () => {
74
+ const result = findConfigFile(testDir)
75
+ expect(result).toBeNull()
76
+ })
77
+ })
78
+
79
+ describe('loadConfigFromPath', () => {
80
+ it('should load valid YAML config', () => {
81
+ const configPath = join(testDir, '.oculum.yaml')
82
+ const configContent = `
83
+ version: 1
84
+ suppressions:
85
+ rules:
86
+ - category: high_entropy_string
87
+ reason: false positives
88
+ findings:
89
+ - hash: "0123456789abcdef"
90
+ file: "src/config.ts"
91
+ reason: static config
92
+ ignore:
93
+ - "**/*.test.ts"
94
+ `
95
+ writeFileSync(configPath, configContent)
96
+
97
+ const result = loadConfigFromPath(configPath)
98
+
99
+ expect(result.found).toBe(true)
100
+ expect(result.errors).toHaveLength(0)
101
+ expect(result.config.version).toBe(1)
102
+ expect(result.config.suppressions?.rules).toHaveLength(1)
103
+ expect(result.config.suppressions?.findings).toHaveLength(1)
104
+ expect(result.config.ignore).toHaveLength(1)
105
+ })
106
+
107
+ it('should load valid JSON config', () => {
108
+ const configPath = join(testDir, 'oculum.config.json')
109
+ const config: SuppressionConfig = {
110
+ version: 1,
111
+ suppressions: {
112
+ rules: [{
113
+ category: 'hardcoded_secret',
114
+ reason: 'test data',
115
+ }],
116
+ },
117
+ }
118
+ writeFileSync(configPath, JSON.stringify(config))
119
+
120
+ const result = loadConfigFromPath(configPath)
121
+
122
+ expect(result.found).toBe(true)
123
+ expect(result.errors).toHaveLength(0)
124
+ expect(result.config.suppressions?.rules).toHaveLength(1)
125
+ })
126
+
127
+ it('should return errors for invalid YAML', () => {
128
+ const configPath = join(testDir, '.oculum.yaml')
129
+ writeFileSync(configPath, '{ invalid yaml }}}')
130
+
131
+ const result = loadConfigFromPath(configPath)
132
+
133
+ expect(result.found).toBe(true)
134
+ expect(result.errors.length).toBeGreaterThan(0)
135
+ })
136
+
137
+ it('should validate required fields', () => {
138
+ const configPath = join(testDir, '.oculum.yaml')
139
+ const configContent = `
140
+ version: 1
141
+ suppressions:
142
+ rules:
143
+ - category: high_entropy_string
144
+ # missing reason
145
+ findings:
146
+ - hash: "abc123"
147
+ # missing file and reason
148
+ `
149
+ writeFileSync(configPath, configContent)
150
+
151
+ const result = loadConfigFromPath(configPath)
152
+
153
+ expect(result.errors.length).toBeGreaterThan(0)
154
+ expect(result.errors.some(e => e.includes('reason'))).toBe(true)
155
+ })
156
+ })
157
+
158
+ describe('loadSuppressionConfig', () => {
159
+ it('should return default config when no file exists', () => {
160
+ const result = loadSuppressionConfig(testDir)
161
+
162
+ expect(result.found).toBe(false)
163
+ expect(result.errors).toHaveLength(0)
164
+ expect(result.config.version).toBe(1)
165
+ expect(result.config.suppressions?.rules).toHaveLength(0)
166
+ expect(result.config.suppressions?.findings).toHaveLength(0)
167
+ })
168
+
169
+ it('should find and load config from project path', () => {
170
+ const configPath = join(testDir, '.oculum.yaml')
171
+ writeFileSync(configPath, 'version: 1\nignore:\n - "*.test.ts"')
172
+
173
+ const result = loadSuppressionConfig(testDir)
174
+
175
+ expect(result.found).toBe(true)
176
+ expect(result.configPath).toBe(configPath)
177
+ expect(result.config.ignore).toContain('*.test.ts')
178
+ })
179
+ })
180
+
181
+ describe('writeSuppressionConfig', () => {
182
+ it('should write YAML config', () => {
183
+ const configPath = join(testDir, '.oculum.yaml')
184
+ const config: SuppressionConfig = {
185
+ version: 1,
186
+ suppressions: {
187
+ findings: [{
188
+ hash: 'abc123def4567890',
189
+ file: 'src/test.ts',
190
+ reason: 'test',
191
+ }],
192
+ },
193
+ }
194
+
195
+ writeSuppressionConfig(configPath, config)
196
+
197
+ const result = loadConfigFromPath(configPath)
198
+ expect(result.config.suppressions?.findings).toHaveLength(1)
199
+ expect(result.config.suppressions?.findings?.[0].hash).toBe('abc123def4567890')
200
+ })
201
+
202
+ it('should write JSON config', () => {
203
+ const configPath = join(testDir, 'oculum.config.json')
204
+ const config: SuppressionConfig = {
205
+ version: 1,
206
+ suppressions: {
207
+ rules: [{
208
+ category: 'hardcoded_secret',
209
+ reason: 'test',
210
+ }],
211
+ },
212
+ }
213
+
214
+ writeSuppressionConfig(configPath, config)
215
+
216
+ const result = loadConfigFromPath(configPath)
217
+ expect(result.config.suppressions?.rules).toHaveLength(1)
218
+ })
219
+ })
220
+
221
+ describe('addFindingSuppression', () => {
222
+ it('should create config file if none exists', () => {
223
+ const suppression: FindingSuppression = {
224
+ hash: 'abc123def4567890',
225
+ file: 'src/test.ts',
226
+ reason: 'test suppression',
227
+ }
228
+
229
+ const result = addFindingSuppression(testDir, suppression)
230
+
231
+ expect(result.success).toBe(true)
232
+ expect(result.configPath).toBe(join(testDir, '.oculum.yaml'))
233
+
234
+ const loaded = loadSuppressionConfig(testDir)
235
+ expect(loaded.config.suppressions?.findings).toHaveLength(1)
236
+ expect(loaded.config.suppressions?.findings?.[0].hash).toBe('abc123def4567890')
237
+ })
238
+
239
+ it('should add to existing config', () => {
240
+ // Create initial config
241
+ writeFileSync(join(testDir, '.oculum.yaml'), `
242
+ version: 1
243
+ suppressions:
244
+ findings:
245
+ - hash: existing123456789
246
+ file: existing.ts
247
+ reason: existing
248
+ `)
249
+
250
+ const suppression: FindingSuppression = {
251
+ hash: 'newone123456789a',
252
+ file: 'new.ts',
253
+ reason: 'new',
254
+ }
255
+
256
+ addFindingSuppression(testDir, suppression)
257
+
258
+ const loaded = loadSuppressionConfig(testDir)
259
+ expect(loaded.config.suppressions?.findings).toHaveLength(2)
260
+ })
261
+
262
+ it('should update existing suppression with same hash', () => {
263
+ const suppression1: FindingSuppression = {
264
+ hash: 'abc123def4567890',
265
+ file: 'src/test.ts',
266
+ reason: 'original reason',
267
+ }
268
+ addFindingSuppression(testDir, suppression1)
269
+
270
+ const suppression2: FindingSuppression = {
271
+ hash: 'abc123def4567890',
272
+ file: 'src/test.ts',
273
+ reason: 'updated reason',
274
+ }
275
+ addFindingSuppression(testDir, suppression2)
276
+
277
+ const loaded = loadSuppressionConfig(testDir)
278
+ expect(loaded.config.suppressions?.findings).toHaveLength(1)
279
+ expect(loaded.config.suppressions?.findings?.[0].reason).toBe('updated reason')
280
+ })
281
+ })
282
+
283
+ describe('removeFindingSuppression', () => {
284
+ it('should remove existing suppression', () => {
285
+ // Create config with suppression
286
+ const suppression: FindingSuppression = {
287
+ hash: 'abc123def4567890',
288
+ file: 'src/test.ts',
289
+ reason: 'test',
290
+ }
291
+ addFindingSuppression(testDir, suppression)
292
+
293
+ const result = removeFindingSuppression(testDir, 'abc123def4567890')
294
+
295
+ expect(result.success).toBe(true)
296
+ expect(result.removed).toBe(true)
297
+
298
+ const loaded = loadSuppressionConfig(testDir)
299
+ expect(loaded.config.suppressions?.findings).toHaveLength(0)
300
+ })
301
+
302
+ it('should return removed=false for non-existent hash', () => {
303
+ // Create config without the hash
304
+ writeFileSync(join(testDir, '.oculum.yaml'), 'version: 1')
305
+
306
+ const result = removeFindingSuppression(testDir, 'nonexistent1234567')
307
+
308
+ expect(result.success).toBe(true)
309
+ expect(result.removed).toBe(false)
310
+ })
311
+
312
+ it('should return success if no config file exists', () => {
313
+ const result = removeFindingSuppression(testDir, 'abc123def4567890')
314
+
315
+ expect(result.success).toBe(true)
316
+ expect(result.removed).toBe(false)
317
+ })
318
+ })
319
+
320
+ describe('listSuppressions', () => {
321
+ it('should return valid response when config file is not found locally', () => {
322
+ // Create a deeply nested path to minimize chance of finding parent config
323
+ const deepDir = join(testDir, 'a', 'b', 'c', 'd', Date.now().toString())
324
+ mkdirSync(deepDir, { recursive: true })
325
+
326
+ const result = listSuppressions(deepDir)
327
+
328
+ // Should return arrays (possibly empty, possibly from a parent config)
329
+ expect(Array.isArray(result.rules)).toBe(true)
330
+ expect(Array.isArray(result.findings)).toBe(true)
331
+ expect(result.errors).toHaveLength(0)
332
+ })
333
+
334
+ it('should return all suppressions from config', () => {
335
+ writeFileSync(join(testDir, '.oculum.yaml'), `
336
+ version: 1
337
+ suppressions:
338
+ rules:
339
+ - category: hardcoded_secret
340
+ reason: test data
341
+ findings:
342
+ - hash: abc123def4567890
343
+ file: test.ts
344
+ reason: suppressed
345
+ `)
346
+
347
+ const result = listSuppressions(testDir)
348
+
349
+ expect(result.rules).toHaveLength(1)
350
+ expect(result.findings).toHaveLength(1)
351
+ expect(result.configPath).toBe(join(testDir, '.oculum.yaml'))
352
+ })
353
+ })
354
+
355
+ describe('isExpired', () => {
356
+ it('should return false for undefined date', () => {
357
+ expect(isExpired(undefined)).toBe(false)
358
+ })
359
+
360
+ it('should return true for past date', () => {
361
+ expect(isExpired('2020-01-01')).toBe(true)
362
+ })
363
+
364
+ it('should return false for future date', () => {
365
+ expect(isExpired('2030-01-01')).toBe(false)
366
+ })
367
+
368
+ it('should handle ISO date strings', () => {
369
+ const futureDate = new Date()
370
+ futureDate.setFullYear(futureDate.getFullYear() + 1)
371
+ expect(isExpired(futureDate.toISOString())).toBe(false)
372
+
373
+ const pastDate = new Date()
374
+ pastDate.setFullYear(pastDate.getFullYear() - 1)
375
+ expect(isExpired(pastDate.toISOString())).toBe(true)
376
+ })
377
+
378
+ it('should return false for invalid date strings', () => {
379
+ expect(isExpired('not-a-date')).toBe(false)
380
+ })
381
+ })
382
+ })
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Hash Computation Tests
3
+ */
4
+
5
+ import {
6
+ computeFindingHash,
7
+ computeHashFromComponents,
8
+ normalizePathForHash,
9
+ normalizeContentForHash,
10
+ isValidHash,
11
+ formatHashForDisplay,
12
+ } from '../hash'
13
+ import type { Vulnerability } from '../../types'
14
+
15
+ describe('Hash Computation', () => {
16
+ describe('normalizePathForHash', () => {
17
+ it('should remove leading ./', () => {
18
+ expect(normalizePathForHash('./src/index.ts')).toBe('src/index.ts')
19
+ })
20
+
21
+ it('should remove leading /', () => {
22
+ expect(normalizePathForHash('/src/index.ts')).toBe('src/index.ts')
23
+ })
24
+
25
+ it('should convert backslashes to forward slashes', () => {
26
+ expect(normalizePathForHash('src\\utils\\helper.ts')).toBe('src/utils/helper.ts')
27
+ })
28
+
29
+ it('should lowercase the path', () => {
30
+ expect(normalizePathForHash('Src/Index.TS')).toBe('src/index.ts')
31
+ })
32
+
33
+ it('should handle complex paths', () => {
34
+ expect(normalizePathForHash('./Src\\Utils/Helper.ts')).toBe('src/utils/helper.ts')
35
+ })
36
+ })
37
+
38
+ describe('normalizeContentForHash', () => {
39
+ it('should trim whitespace', () => {
40
+ expect(normalizeContentForHash(' const x = 1 ')).toBe('const x = 1')
41
+ })
42
+
43
+ it('should collapse multiple whitespace', () => {
44
+ expect(normalizeContentForHash('const x = 1')).toBe('const x = 1')
45
+ })
46
+
47
+ it('should remove line number prefix', () => {
48
+ expect(normalizeContentForHash('123:\tconst x = 1')).toBe('const x = 1')
49
+ })
50
+
51
+ it('should handle newlines', () => {
52
+ expect(normalizeContentForHash('const x\n = 1')).toBe('const x = 1')
53
+ })
54
+ })
55
+
56
+ describe('computeFindingHash', () => {
57
+ const mockFinding: Vulnerability = {
58
+ id: 'test-1',
59
+ filePath: 'src/index.ts',
60
+ lineNumber: 10,
61
+ lineContent: 'const secret = "api_key_12345"',
62
+ severity: 'high',
63
+ category: 'hardcoded_secret',
64
+ title: 'Hardcoded Secret',
65
+ description: 'Found a hardcoded secret',
66
+ confidence: 'high',
67
+ layer: 1,
68
+ }
69
+
70
+ it('should produce a 16-character hex hash', () => {
71
+ const hash = computeFindingHash(mockFinding)
72
+ expect(hash).toHaveLength(16)
73
+ expect(/^[0-9a-f]{16}$/.test(hash)).toBe(true)
74
+ })
75
+
76
+ it('should produce consistent hashes for the same finding', () => {
77
+ const hash1 = computeFindingHash(mockFinding)
78
+ const hash2 = computeFindingHash(mockFinding)
79
+ expect(hash1).toBe(hash2)
80
+ })
81
+
82
+ it('should produce different hashes for different content', () => {
83
+ const finding1 = { ...mockFinding, lineContent: 'const a = 1' }
84
+ const finding2 = { ...mockFinding, lineContent: 'const b = 2' }
85
+ expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
86
+ })
87
+
88
+ it('should produce different hashes for different categories', () => {
89
+ const finding1 = { ...mockFinding, category: 'hardcoded_secret' as const }
90
+ const finding2 = { ...mockFinding, category: 'high_entropy_string' as const }
91
+ expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
92
+ })
93
+
94
+ it('should produce different hashes for different file paths', () => {
95
+ const finding1 = { ...mockFinding, filePath: 'src/a.ts' }
96
+ const finding2 = { ...mockFinding, filePath: 'src/b.ts' }
97
+ expect(computeFindingHash(finding1)).not.toBe(computeFindingHash(finding2))
98
+ })
99
+
100
+ it('should be stable across whitespace variations in content', () => {
101
+ const finding1 = { ...mockFinding, lineContent: 'const x = 1' }
102
+ const finding2 = { ...mockFinding, lineContent: ' const x = 1 ' }
103
+ expect(computeFindingHash(finding1)).toBe(computeFindingHash(finding2))
104
+ })
105
+
106
+ it('should be stable across path format variations', () => {
107
+ const finding1 = { ...mockFinding, filePath: './src/index.ts' }
108
+ const finding2 = { ...mockFinding, filePath: 'src/index.ts' }
109
+ expect(computeFindingHash(finding1)).toBe(computeFindingHash(finding2))
110
+ })
111
+ })
112
+
113
+ describe('computeHashFromComponents', () => {
114
+ it('should produce consistent hashes', () => {
115
+ const hash1 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
116
+ const hash2 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
117
+ expect(hash1).toBe(hash2)
118
+ })
119
+
120
+ it('should match computeFindingHash for equivalent inputs', () => {
121
+ const finding: Vulnerability = {
122
+ id: 'test',
123
+ filePath: 'src/index.ts',
124
+ lineNumber: 1,
125
+ lineContent: 'const x = 1',
126
+ severity: 'high',
127
+ category: 'hardcoded_secret',
128
+ title: 'Test',
129
+ description: 'Test',
130
+ confidence: 'high',
131
+ layer: 1,
132
+ }
133
+
134
+ const hash1 = computeFindingHash(finding)
135
+ const hash2 = computeHashFromComponents('src/index.ts', 'const x = 1', 'hardcoded_secret')
136
+ expect(hash1).toBe(hash2)
137
+ })
138
+ })
139
+
140
+ describe('isValidHash', () => {
141
+ it('should accept valid 16-char hex hashes', () => {
142
+ expect(isValidHash('0123456789abcdef')).toBe(true)
143
+ expect(isValidHash('ABCDEF0123456789')).toBe(true)
144
+ expect(isValidHash('aAbBcCdDeEfF0011')).toBe(true)
145
+ })
146
+
147
+ it('should reject invalid hashes', () => {
148
+ expect(isValidHash('')).toBe(false)
149
+ expect(isValidHash('tooshort')).toBe(false)
150
+ expect(isValidHash('0123456789abcdefghij')).toBe(false) // too long
151
+ expect(isValidHash('0123456789abcdeg')).toBe(false) // invalid char 'g'
152
+ expect(isValidHash('0123456789abcde!')).toBe(false) // invalid char '!'
153
+ })
154
+ })
155
+
156
+ describe('formatHashForDisplay', () => {
157
+ it('should return short hashes unchanged', () => {
158
+ expect(formatHashForDisplay('abcd1234')).toBe('abcd1234')
159
+ expect(formatHashForDisplay('0123456789abcdef')).toBe('0123456789abcdef')
160
+ })
161
+
162
+ it('should truncate long hashes', () => {
163
+ expect(formatHashForDisplay('0123456789abcdef0123')).toBe('0123456789ab...')
164
+ })
165
+ })
166
+ })