@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,225 @@
1
+ /**
2
+ * Baseline Manager Tests
3
+ */
4
+
5
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs'
6
+ import { join } from 'path'
7
+ import { tmpdir } from 'os'
8
+ import { BaselineManager } from '../manager'
9
+ import type { ScanResult } from '../../types'
10
+ import type { BaselineData } from '../types'
11
+
12
+ describe('BaselineManager', () => {
13
+ let testDir: string
14
+ let manager: BaselineManager
15
+
16
+ beforeEach(() => {
17
+ // Create a unique temp directory for each test
18
+ testDir = join(tmpdir(), `baseline-test-${Date.now()}-${Math.random().toString(36).slice(2)}`)
19
+ mkdirSync(testDir, { recursive: true })
20
+ manager = new BaselineManager({ projectPath: testDir })
21
+ })
22
+
23
+ afterEach(() => {
24
+ // Clean up test directory
25
+ try {
26
+ rmSync(testDir, { recursive: true, force: true })
27
+ } catch {
28
+ // Ignore cleanup errors
29
+ }
30
+ })
31
+
32
+ describe('loadBaseline', () => {
33
+ it('should return found: false when no baseline exists', () => {
34
+ const result = manager.loadBaseline()
35
+ expect(result.found).toBe(false)
36
+ expect(result.baseline).toBeUndefined()
37
+ expect(result.error).toBeUndefined()
38
+ })
39
+
40
+ it('should load a valid baseline', () => {
41
+ // Create a valid baseline file
42
+ const baseline: BaselineData = {
43
+ version: 1,
44
+ createdAt: '2024-01-15T10:00:00.000Z',
45
+ commit: 'abc1234',
46
+ branch: 'main',
47
+ findings: [
48
+ {
49
+ hash: 'abcd1234abcd1234',
50
+ filePath: 'src/test.ts',
51
+ lineNumber: 10,
52
+ category: 'hardcoded_secret',
53
+ severity: 'critical',
54
+ title: 'Hardcoded API key',
55
+ },
56
+ ],
57
+ stats: {
58
+ total: 1,
59
+ critical: 1,
60
+ high: 0,
61
+ medium: 0,
62
+ low: 0,
63
+ info: 0,
64
+ },
65
+ }
66
+
67
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
68
+ writeFileSync(join(testDir, '.oculum/baseline.json'), JSON.stringify(baseline))
69
+
70
+ const result = manager.loadBaseline()
71
+ expect(result.found).toBe(true)
72
+ expect(result.baseline).toEqual(baseline)
73
+ expect(result.error).toBeUndefined()
74
+ })
75
+
76
+ it('should return error for invalid JSON', () => {
77
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
78
+ writeFileSync(join(testDir, '.oculum/baseline.json'), 'not valid json')
79
+
80
+ const result = manager.loadBaseline()
81
+ expect(result.found).toBe(false)
82
+ expect(result.error).toContain('Failed to parse baseline')
83
+ })
84
+
85
+ it('should return error for unsupported version', () => {
86
+ const baseline = {
87
+ version: 99,
88
+ createdAt: '2024-01-15T10:00:00.000Z',
89
+ findings: [],
90
+ stats: { total: 0, critical: 0, high: 0, medium: 0, low: 0, info: 0 },
91
+ }
92
+
93
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
94
+ writeFileSync(join(testDir, '.oculum/baseline.json'), JSON.stringify(baseline))
95
+
96
+ const result = manager.loadBaseline()
97
+ expect(result.found).toBe(false)
98
+ expect(result.error).toContain('Unsupported baseline version')
99
+ })
100
+
101
+ it('should return error when findings array is missing', () => {
102
+ const baseline = {
103
+ version: 1,
104
+ createdAt: '2024-01-15T10:00:00.000Z',
105
+ stats: { total: 0, critical: 0, high: 0, medium: 0, low: 0, info: 0 },
106
+ }
107
+
108
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
109
+ writeFileSync(join(testDir, '.oculum/baseline.json'), JSON.stringify(baseline))
110
+
111
+ const result = manager.loadBaseline()
112
+ expect(result.found).toBe(false)
113
+ expect(result.error).toContain('missing findings array')
114
+ })
115
+ })
116
+
117
+ describe('saveBaseline', () => {
118
+ it('should save a baseline from scan result', () => {
119
+ const scanResult: ScanResult = {
120
+ repoName: 'test/repo',
121
+ repoUrl: 'https://github.com/test/repo',
122
+ branch: 'main',
123
+ filesScanned: 10,
124
+ filesSkipped: 2,
125
+ vulnerabilities: [
126
+ {
127
+ filePath: 'src/test.ts',
128
+ lineNumber: 10,
129
+ category: 'hardcoded_secret',
130
+ severity: 'critical',
131
+ title: 'Hardcoded API key',
132
+ description: 'Test description',
133
+ confidence: 'high',
134
+ layer: 1,
135
+ lineContent: 'const API_KEY = "sk-1234567890"',
136
+ },
137
+ ],
138
+ severityCounts: { critical: 1, high: 0, medium: 0, low: 0, info: 0 },
139
+ categoryCounts: {},
140
+ hasBlockingIssues: true,
141
+ scanDuration: 1000,
142
+ timestamp: '2024-01-15T10:00:00.000Z',
143
+ }
144
+
145
+ const result = manager.saveBaseline(scanResult, { scanDepth: 'cheap' })
146
+
147
+ expect(result.success).toBe(true)
148
+ expect(existsSync(result.path)).toBe(true)
149
+
150
+ // Verify saved content
151
+ const content = readFileSync(result.path, 'utf-8')
152
+ const baseline = JSON.parse(content) as BaselineData
153
+
154
+ expect(baseline.version).toBe(1)
155
+ expect(baseline.findings).toHaveLength(1)
156
+ expect(baseline.findings[0].title).toBe('Hardcoded API key')
157
+ expect(baseline.stats.critical).toBe(1)
158
+ expect(baseline.scanDepth).toBe('cheap')
159
+ })
160
+
161
+ it('should create .oculum directory if it does not exist', () => {
162
+ const scanResult: ScanResult = {
163
+ repoName: 'test/repo',
164
+ repoUrl: 'https://github.com/test/repo',
165
+ branch: 'main',
166
+ filesScanned: 0,
167
+ filesSkipped: 0,
168
+ vulnerabilities: [],
169
+ severityCounts: { critical: 0, high: 0, medium: 0, low: 0, info: 0 },
170
+ categoryCounts: {},
171
+ hasBlockingIssues: false,
172
+ scanDuration: 100,
173
+ timestamp: '2024-01-15T10:00:00.000Z',
174
+ }
175
+
176
+ expect(existsSync(join(testDir, '.oculum'))).toBe(false)
177
+
178
+ const result = manager.saveBaseline(scanResult)
179
+
180
+ expect(result.success).toBe(true)
181
+ expect(existsSync(join(testDir, '.oculum'))).toBe(true)
182
+ })
183
+ })
184
+
185
+ describe('clearBaseline', () => {
186
+ it('should return success with existed: false when no baseline exists', () => {
187
+ const result = manager.clearBaseline()
188
+ expect(result.success).toBe(true)
189
+ expect(result.existed).toBe(false)
190
+ })
191
+
192
+ it('should delete existing baseline', () => {
193
+ // Create baseline file
194
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
195
+ writeFileSync(join(testDir, '.oculum/baseline.json'), '{}')
196
+
197
+ expect(existsSync(join(testDir, '.oculum/baseline.json'))).toBe(true)
198
+
199
+ const result = manager.clearBaseline()
200
+
201
+ expect(result.success).toBe(true)
202
+ expect(result.existed).toBe(true)
203
+ expect(existsSync(join(testDir, '.oculum/baseline.json'))).toBe(false)
204
+ })
205
+ })
206
+
207
+ describe('hasBaseline', () => {
208
+ it('should return false when no baseline exists', () => {
209
+ expect(manager.hasBaseline()).toBe(false)
210
+ })
211
+
212
+ it('should return true when baseline exists', () => {
213
+ mkdirSync(join(testDir, '.oculum'), { recursive: true })
214
+ writeFileSync(join(testDir, '.oculum/baseline.json'), '{}')
215
+
216
+ expect(manager.hasBaseline()).toBe(true)
217
+ })
218
+ })
219
+
220
+ describe('getBaselinePath', () => {
221
+ it('should return correct path', () => {
222
+ expect(manager.getBaselinePath()).toBe(join(testDir, '.oculum/baseline.json'))
223
+ })
224
+ })
225
+ })
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Baseline Diff Computation
3
+ * Computes the difference between current findings and a baseline
4
+ */
5
+
6
+ import type { Vulnerability, SeverityCounts, VulnerabilitySeverity } from '../types'
7
+ import type { BaselineData, BaselineFinding, DiffResult } from './types'
8
+ import { computeFindingHash } from '../suppression/hash'
9
+
10
+ /**
11
+ * Compute severity counts from baseline findings
12
+ */
13
+ function computeBaselineSeverityCounts(findings: BaselineFinding[]): SeverityCounts {
14
+ const counts: SeverityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
15
+
16
+ for (const finding of findings) {
17
+ const severity = finding.severity as VulnerabilitySeverity
18
+ if (severity in counts) {
19
+ counts[severity]++
20
+ }
21
+ }
22
+
23
+ return counts
24
+ }
25
+
26
+ /**
27
+ * Compute severity counts from vulnerabilities
28
+ */
29
+ function computeVulnerabilitySeverityCounts(vulnerabilities: Vulnerability[]): SeverityCounts {
30
+ const counts: SeverityCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0 }
31
+
32
+ for (const vuln of vulnerabilities) {
33
+ if (vuln.severity in counts) {
34
+ counts[vuln.severity]++
35
+ }
36
+ }
37
+
38
+ return counts
39
+ }
40
+
41
+ /**
42
+ * Compute the diff between current scan findings and a baseline
43
+ *
44
+ * Uses finding hashes for comparison, which are computed from:
45
+ * - Normalized file path
46
+ * - Normalized line content
47
+ * - Category
48
+ *
49
+ * This means findings are considered the same even if:
50
+ * - Line numbers changed (code moved)
51
+ * - Minor whitespace changes occurred
52
+ *
53
+ * @param currentFindings - Vulnerabilities from the current scan
54
+ * @param baseline - The baseline to compare against
55
+ * @returns DiffResult with new, fixed, and existing findings
56
+ */
57
+ export function computeDiff(
58
+ currentFindings: Vulnerability[],
59
+ baseline: BaselineData
60
+ ): DiffResult {
61
+ // Build hash set from baseline for O(1) lookup
62
+ const baselineHashes = new Set(baseline.findings.map(f => f.hash))
63
+
64
+ // Build hash map from current findings
65
+ const currentHashMap = new Map<string, Vulnerability>()
66
+ for (const finding of currentFindings) {
67
+ const hash = computeFindingHash(finding)
68
+ currentHashMap.set(hash, finding)
69
+ }
70
+
71
+ // Compute new findings (in current, not in baseline)
72
+ const newFindings: Vulnerability[] = []
73
+ for (const finding of currentFindings) {
74
+ const hash = computeFindingHash(finding)
75
+ if (!baselineHashes.has(hash)) {
76
+ newFindings.push(finding)
77
+ }
78
+ }
79
+
80
+ // Compute fixed findings (in baseline, not in current)
81
+ const fixedFindings: BaselineFinding[] = []
82
+ for (const baselineFinding of baseline.findings) {
83
+ if (!currentHashMap.has(baselineFinding.hash)) {
84
+ fixedFindings.push(baselineFinding)
85
+ }
86
+ }
87
+
88
+ // Compute existing findings (in both)
89
+ const existingFindings: Vulnerability[] = []
90
+ for (const finding of currentFindings) {
91
+ const hash = computeFindingHash(finding)
92
+ if (baselineHashes.has(hash)) {
93
+ existingFindings.push(finding)
94
+ }
95
+ }
96
+
97
+ return {
98
+ new: newFindings,
99
+ fixed: fixedFindings,
100
+ existing: existingFindings,
101
+ stats: {
102
+ newCount: newFindings.length,
103
+ fixedCount: fixedFindings.length,
104
+ existingCount: existingFindings.length,
105
+ newBySeverity: computeVulnerabilitySeverityCounts(newFindings),
106
+ fixedBySeverity: computeBaselineSeverityCounts(fixedFindings),
107
+ },
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Check if a diff has any new blocking issues (critical or high severity)
113
+ */
114
+ export function hasNewBlockingIssues(diff: DiffResult): boolean {
115
+ return diff.stats.newBySeverity.critical > 0 || diff.stats.newBySeverity.high > 0
116
+ }
117
+
118
+ /**
119
+ * Format a summary string for the diff
120
+ */
121
+ export function formatDiffSummary(diff: DiffResult): string {
122
+ const parts: string[] = []
123
+
124
+ if (diff.stats.newCount > 0) {
125
+ parts.push(`${diff.stats.newCount} new`)
126
+ }
127
+ if (diff.stats.fixedCount > 0) {
128
+ parts.push(`${diff.stats.fixedCount} fixed`)
129
+ }
130
+ if (diff.stats.existingCount > 0) {
131
+ parts.push(`${diff.stats.existingCount} existing`)
132
+ }
133
+
134
+ return parts.join(', ')
135
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Baseline Module
3
+ * Provides baseline/diff mode functionality for tracking security improvements
4
+ */
5
+
6
+ // Types
7
+ export type {
8
+ BaselineFinding,
9
+ BaselineData,
10
+ DiffResult,
11
+ BaselineDiff,
12
+ } from './types'
13
+ export { BASELINE_FILE_PATH, OCULUM_DIR } from './types'
14
+
15
+ // Manager
16
+ export {
17
+ BaselineManager,
18
+ type BaselineManagerOptions,
19
+ type LoadBaselineResult,
20
+ type SaveBaselineResult,
21
+ type ClearBaselineResult,
22
+ } from './manager'
23
+
24
+ // Diff computation
25
+ export {
26
+ computeDiff,
27
+ hasNewBlockingIssues,
28
+ formatDiffSummary,
29
+ } from './diff'
@@ -0,0 +1,230 @@
1
+ /**
2
+ * Baseline Manager
3
+ * Handles loading, saving, and clearing baseline files
4
+ */
5
+
6
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs'
7
+ import { join, dirname } from 'path'
8
+ import { execFileSync } from 'child_process'
9
+ import type { ScanResult, ScanDepth, Vulnerability } from '../types'
10
+ import type { BaselineData, BaselineFinding } from './types'
11
+ import { BASELINE_FILE_PATH, OCULUM_DIR } from './types'
12
+ import { computeFindingHash } from '../suppression/hash'
13
+
14
+ export interface BaselineManagerOptions {
15
+ /** Project root path */
16
+ projectPath: string
17
+ }
18
+
19
+ export interface LoadBaselineResult {
20
+ /** Whether a baseline was found */
21
+ found: boolean
22
+ /** The baseline data (if found) */
23
+ baseline?: BaselineData
24
+ /** Error message (if failed to load) */
25
+ error?: string
26
+ }
27
+
28
+ export interface SaveBaselineResult {
29
+ /** Whether the save was successful */
30
+ success: boolean
31
+ /** Path where baseline was saved */
32
+ path: string
33
+ /** Error message (if failed) */
34
+ error?: string
35
+ }
36
+
37
+ export interface ClearBaselineResult {
38
+ /** Whether the clear was successful */
39
+ success: boolean
40
+ /** Whether a baseline existed before clearing */
41
+ existed: boolean
42
+ /** Error message (if failed) */
43
+ error?: string
44
+ }
45
+
46
+ /**
47
+ * Get current git commit SHA (short form)
48
+ */
49
+ function getGitCommit(projectPath: string): string | undefined {
50
+ try {
51
+ const result = execFileSync('git', ['rev-parse', '--short', 'HEAD'], {
52
+ cwd: projectPath,
53
+ stdio: ['ignore', 'pipe', 'ignore'],
54
+ })
55
+ return result.toString().trim()
56
+ } catch {
57
+ return undefined
58
+ }
59
+ }
60
+
61
+ /**
62
+ * Get current git branch name
63
+ */
64
+ function getGitBranch(projectPath: string): string | undefined {
65
+ try {
66
+ const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
67
+ cwd: projectPath,
68
+ stdio: ['ignore', 'pipe', 'ignore'],
69
+ })
70
+ return result.toString().trim()
71
+ } catch {
72
+ return undefined
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Convert a Vulnerability to a BaselineFinding
78
+ */
79
+ function toBaselineFinding(vuln: Vulnerability): BaselineFinding {
80
+ return {
81
+ hash: computeFindingHash(vuln),
82
+ filePath: vuln.filePath,
83
+ lineNumber: vuln.lineNumber,
84
+ category: vuln.category,
85
+ severity: vuln.severity,
86
+ title: vuln.title,
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Manages baseline files for diff mode
92
+ */
93
+ export class BaselineManager {
94
+ private projectPath: string
95
+ private baselinePath: string
96
+
97
+ constructor(options: BaselineManagerOptions | string) {
98
+ // Support both old string arg and new options object
99
+ if (typeof options === 'string') {
100
+ this.projectPath = options
101
+ } else {
102
+ this.projectPath = options.projectPath
103
+ }
104
+ this.baselinePath = join(this.projectPath, BASELINE_FILE_PATH)
105
+ }
106
+
107
+ /**
108
+ * Get the full path to the baseline file
109
+ */
110
+ getBaselinePath(): string {
111
+ return this.baselinePath
112
+ }
113
+
114
+ /**
115
+ * Load baseline from .oculum/baseline.json
116
+ */
117
+ loadBaseline(): LoadBaselineResult {
118
+ if (!existsSync(this.baselinePath)) {
119
+ return { found: false }
120
+ }
121
+
122
+ try {
123
+ const content = readFileSync(this.baselinePath, 'utf-8')
124
+ const baseline = JSON.parse(content) as BaselineData
125
+
126
+ // Basic validation
127
+ if (baseline.version !== 1) {
128
+ return {
129
+ found: false,
130
+ error: `Unsupported baseline version: ${baseline.version}. Expected version 1.`,
131
+ }
132
+ }
133
+
134
+ if (!Array.isArray(baseline.findings)) {
135
+ return {
136
+ found: false,
137
+ error: 'Invalid baseline: missing findings array',
138
+ }
139
+ }
140
+
141
+ return { found: true, baseline }
142
+ } catch (err) {
143
+ return {
144
+ found: false,
145
+ error: `Failed to parse baseline: ${err instanceof Error ? err.message : 'Unknown error'}`,
146
+ }
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Save current scan result as baseline
152
+ */
153
+ saveBaseline(
154
+ scanResult: ScanResult,
155
+ options?: { commit?: string; branch?: string; scanDepth?: ScanDepth }
156
+ ): SaveBaselineResult {
157
+ try {
158
+ // Ensure .oculum directory exists
159
+ const oculumDir = join(this.projectPath, OCULUM_DIR)
160
+ if (!existsSync(oculumDir)) {
161
+ mkdirSync(oculumDir, { recursive: true })
162
+ }
163
+
164
+ // Get git info if not provided
165
+ const commit = options?.commit ?? getGitCommit(this.projectPath)
166
+ const branch = options?.branch ?? getGitBranch(this.projectPath)
167
+
168
+ // Convert vulnerabilities to baseline findings
169
+ const findings = scanResult.vulnerabilities.map(toBaselineFinding)
170
+
171
+ // Build baseline data
172
+ const baseline: BaselineData = {
173
+ version: 1,
174
+ createdAt: new Date().toISOString(),
175
+ commit,
176
+ branch,
177
+ scanDepth: options?.scanDepth,
178
+ findings,
179
+ stats: {
180
+ total: findings.length,
181
+ critical: scanResult.severityCounts.critical,
182
+ high: scanResult.severityCounts.high,
183
+ medium: scanResult.severityCounts.medium,
184
+ low: scanResult.severityCounts.low,
185
+ info: scanResult.severityCounts.info,
186
+ },
187
+ }
188
+
189
+ // Write to file
190
+ writeFileSync(this.baselinePath, JSON.stringify(baseline, null, 2))
191
+
192
+ return { success: true, path: this.baselinePath }
193
+ } catch (err) {
194
+ return {
195
+ success: false,
196
+ path: this.baselinePath,
197
+ error: `Failed to save baseline: ${err instanceof Error ? err.message : 'Unknown error'}`,
198
+ }
199
+ }
200
+ }
201
+
202
+ /**
203
+ * Clear (delete) the baseline file
204
+ */
205
+ clearBaseline(): ClearBaselineResult {
206
+ const existed = existsSync(this.baselinePath)
207
+
208
+ if (!existed) {
209
+ return { success: true, existed: false }
210
+ }
211
+
212
+ try {
213
+ unlinkSync(this.baselinePath)
214
+ return { success: true, existed: true }
215
+ } catch (err) {
216
+ return {
217
+ success: false,
218
+ existed: true,
219
+ error: `Failed to clear baseline: ${err instanceof Error ? err.message : 'Unknown error'}`,
220
+ }
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Check if a baseline exists
226
+ */
227
+ hasBaseline(): boolean {
228
+ return existsSync(this.baselinePath)
229
+ }
230
+ }