@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,97 @@
1
+ /**
2
+ * Baseline Types
3
+ * Types for baseline/diff mode functionality
4
+ */
5
+
6
+ import type { VulnerabilityCategory, VulnerabilitySeverity, SeverityCounts, ScanDepth } from '../types'
7
+
8
+ /**
9
+ * A finding stored in the baseline
10
+ * Contains enough information to identify and display the finding
11
+ */
12
+ export interface BaselineFinding {
13
+ /** Finding hash (from computeFindingHash) */
14
+ hash: string
15
+ /** File path relative to project root */
16
+ filePath: string
17
+ /** Line number in the file */
18
+ lineNumber: number
19
+ /** Vulnerability category */
20
+ category: VulnerabilityCategory
21
+ /** Severity level */
22
+ severity: VulnerabilitySeverity
23
+ /** Finding title */
24
+ title: string
25
+ }
26
+
27
+ /**
28
+ * Baseline data stored in .oculum/baseline.json
29
+ */
30
+ export interface BaselineData {
31
+ /** Schema version for forward compatibility */
32
+ version: 1
33
+ /** ISO 8601 timestamp when baseline was created */
34
+ createdAt: string
35
+ /** Git commit SHA when baseline was created (optional) */
36
+ commit?: string
37
+ /** Git branch name when baseline was created (optional) */
38
+ branch?: string
39
+ /** Scan depth used when creating baseline */
40
+ scanDepth?: ScanDepth
41
+ /** List of findings in the baseline */
42
+ findings: BaselineFinding[]
43
+ /** Summary statistics */
44
+ stats: {
45
+ total: number
46
+ critical: number
47
+ high: number
48
+ medium: number
49
+ low: number
50
+ info: number
51
+ }
52
+ }
53
+
54
+ /**
55
+ * Result of comparing current findings against baseline
56
+ */
57
+ export interface DiffResult {
58
+ /** Findings in current scan but NOT in baseline (new issues) */
59
+ new: import('../types').Vulnerability[]
60
+ /** Findings in baseline but NOT in current scan (fixed issues) */
61
+ fixed: BaselineFinding[]
62
+ /** Findings in both current scan AND baseline (existing issues) */
63
+ existing: import('../types').Vulnerability[]
64
+ /** Summary statistics */
65
+ stats: {
66
+ newCount: number
67
+ fixedCount: number
68
+ existingCount: number
69
+ newBySeverity: SeverityCounts
70
+ fixedBySeverity: SeverityCounts
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Baseline diff metadata attached to ScanResult
76
+ * Only present when --new flag is used
77
+ */
78
+ export interface BaselineDiff {
79
+ /** When the baseline was created */
80
+ baselineCreatedAt: string
81
+ /** Git commit of the baseline (if available) */
82
+ baselineCommit?: string
83
+ /** Number of new findings (not in baseline) */
84
+ newCount: number
85
+ /** Number of fixed findings (in baseline, not in current) */
86
+ fixedCount: number
87
+ /** Number of existing findings (in both) */
88
+ existingCount: number
89
+ /** Details of fixed findings for display */
90
+ fixedFindings: BaselineFinding[]
91
+ }
92
+
93
+ /** Default baseline file path relative to project root */
94
+ export const BASELINE_FILE_PATH = '.oculum/baseline.json'
95
+
96
+ /** Directory for oculum files */
97
+ export const OCULUM_DIR = '.oculum'
@@ -5,6 +5,7 @@
5
5
 
6
6
  import type { ScanResult, Vulnerability, VulnerabilitySeverity } from '../types'
7
7
  import { groupByTheme, getBlockingIssues, GroupedFindings, THEME_CONFIG } from './grouping'
8
+ import { computeFindingHash } from '../suppression/hash'
8
9
 
9
10
  /**
10
11
  * ANSI color codes
@@ -57,28 +58,101 @@ function severityBadge(severity: VulnerabilitySeverity): string {
57
58
  return c(style.color, `${style.symbol} ${style.label}`)
58
59
  }
59
60
 
61
+ /**
62
+ * Format options for single finding
63
+ */
64
+ interface FormatFindingOptions {
65
+ indent?: string
66
+ compact?: boolean
67
+ verbose?: boolean
68
+ }
69
+
60
70
  /**
61
71
  * Format a single finding for terminal
72
+ * Default: Actionable output with impact, code, and fix steps
73
+ * Compact: Severity + title + location only
74
+ * Verbose: All of the above plus references and validation notes
62
75
  */
63
- function formatFinding(finding: Vulnerability, indent: string = ' '): string {
76
+ function formatFinding(finding: Vulnerability, options: FormatFindingOptions = {}): string {
77
+ const { indent = ' ', compact = false, verbose = false } = options
64
78
  const badge = severityBadge(finding.severity)
65
79
  const location = c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
80
+ const hash = computeFindingHash(finding)
66
81
 
82
+ // Compact mode: just severity, title, and location
83
+ if (compact) {
84
+ return `${indent}${badge} ${c(colors.bold, finding.title)} ${location}\n`
85
+ }
86
+
87
+ // Default actionable output
67
88
  let output = `${indent}${badge} ${c(colors.bold, finding.title)}\n`
68
89
  output += `${indent} ${location}\n`
69
- output += `${indent} ${c(colors.dim, finding.description)}\n`
90
+ output += '\n'
91
+
92
+ // Impact (why this matters) - shown by default
93
+ if (finding.impact) {
94
+ output += `${indent} ${c(colors.yellow + colors.bold, 'Impact:')} ${finding.impact}\n`
95
+ output += '\n'
96
+ }
97
+
98
+ // Code snippet
99
+ if (finding.lineContent && finding.lineContent.trim()) {
100
+ output += `${indent} ${c(colors.dim, 'Code:')} ${c(colors.white, finding.lineContent.trim().substring(0, 80))}${finding.lineContent.trim().length > 80 ? '...' : ''}\n`
101
+ output += '\n'
102
+ }
70
103
 
71
- if (finding.suggestedFix) {
104
+ // Fix steps - shown by default (numbered list)
105
+ if (finding.fixSteps && finding.fixSteps.length > 0) {
106
+ output += `${indent} ${c(colors.green + colors.bold, 'Fix:')}\n`
107
+ finding.fixSteps.forEach((step, i) => {
108
+ output += `${indent} ${c(colors.green, `${i + 1}. ${step}`)}\n`
109
+ })
110
+ output += '\n'
111
+ } else if (finding.suggestedFix) {
112
+ // Fallback to legacy suggestedFix field
72
113
  output += `${indent} ${c(colors.green, '💡 ' + finding.suggestedFix)}\n`
114
+ output += '\n'
73
115
  }
74
116
 
117
+ // Verbose mode: show additional details
118
+ if (verbose) {
119
+ // Description
120
+ output += `${indent} ${c(colors.dim, finding.description)}\n`
121
+
122
+ // References (OWASP/CWE links)
123
+ if (finding.references && finding.references.length > 0) {
124
+ output += `${indent} ${c(colors.blue, 'References:')}\n`
125
+ finding.references.forEach(ref => {
126
+ output += `${indent} ${c(colors.blue, ` • ${ref}`)}\n`
127
+ })
128
+ }
129
+
130
+ // Validation notes (if AI validated)
131
+ if (finding.validationNotes) {
132
+ output += `${indent} ${c(colors.dim, `[AI] ${finding.validationNotes}`)}\n`
133
+ }
134
+
135
+ // AI enhanced indicator
136
+ if (finding.aiEnhanced) {
137
+ output += `${indent} ${c(colors.magenta, '✨ AI-enhanced fix suggestion')}\n`
138
+ }
139
+ }
140
+
141
+ // Suppress command - always shown
142
+ output += `${indent} ${c(colors.dim, `Suppress: oculum ignore ${hash} --file "${finding.filePath}:${finding.lineNumber}" --reason "..."`)}\n`
143
+
75
144
  return output
76
145
  }
77
146
 
78
147
  /**
79
148
  * Format a group of findings
80
149
  */
81
- function formatGroup(group: GroupedFindings, maxFindings: number = 10): string {
150
+ function formatGroup(group: GroupedFindings, options: {
151
+ maxFindings?: number
152
+ compact?: boolean
153
+ verbose?: boolean
154
+ } = {}): string {
155
+ const { maxFindings = 10, compact = false, verbose = false } = options
82
156
  const { theme, themeName, findings, severityCounts } = group
83
157
  const config = THEME_CONFIG[theme]
84
158
 
@@ -96,7 +170,7 @@ function formatGroup(group: GroupedFindings, maxFindings: number = 10): string {
96
170
  // Show findings
97
171
  const shown = findings.slice(0, maxFindings)
98
172
  for (const finding of shown) {
99
- output += formatFinding(finding) + '\n'
173
+ output += formatFinding(finding, { compact, verbose }) + '\n'
100
174
  }
101
175
 
102
176
  // Truncation notice
@@ -107,6 +181,32 @@ function formatGroup(group: GroupedFindings, maxFindings: number = 10): string {
107
181
  return output
108
182
  }
109
183
 
184
+ /**
185
+ * Format baseline diff summary
186
+ */
187
+ function formatDiffSummary(baselineDiff: NonNullable<ScanResult['baselineDiff']>): string {
188
+ let output = ''
189
+
190
+ output += c(colors.bold, 'Baseline Comparison') + '\n'
191
+ output += c(colors.dim, '─'.repeat(40)) + '\n'
192
+ output += ` 🆕 ${c(colors.yellow, `${baselineDiff.newCount} new`)} findings\n`
193
+ output += ` ✅ ${c(colors.green, `${baselineDiff.fixedCount} fixed`)} since baseline\n`
194
+ output += ` 📋 ${c(colors.dim, `${baselineDiff.existingCount} existing`)} (in baseline)\n`
195
+ output += '\n'
196
+
197
+ // Format baseline date
198
+ const baselineDate = new Date(baselineDiff.baselineCreatedAt)
199
+ const dateStr = baselineDate.toLocaleDateString('en-US', {
200
+ year: 'numeric',
201
+ month: 'short',
202
+ day: 'numeric',
203
+ })
204
+ const commitStr = baselineDiff.baselineCommit ? ` (${baselineDiff.baselineCommit})` : ''
205
+ output += c(colors.dim, `Baseline from ${dateStr}${commitStr}`) + '\n\n'
206
+
207
+ return output
208
+ }
209
+
110
210
  /**
111
211
  * Format full scan result for terminal
112
212
  */
@@ -114,13 +214,17 @@ export function formatTerminalOutput(result: ScanResult, options: {
114
214
  maxFindingsPerGroup?: number
115
215
  showAllFindings?: boolean
116
216
  noColor?: boolean
217
+ compact?: boolean
218
+ verbose?: boolean
117
219
  } = {}): string {
118
220
  const {
119
221
  maxFindingsPerGroup = 10,
120
222
  showAllFindings = false,
223
+ compact = false,
224
+ verbose = false,
121
225
  } = options
122
226
 
123
- const { vulnerabilities, severityCounts, hasBlockingIssues, filesScanned, scanDuration } = result
227
+ const { vulnerabilities, severityCounts, hasBlockingIssues, filesScanned, scanDuration, baselineDiff } = result
124
228
 
125
229
  let output = '\n'
126
230
 
@@ -129,6 +233,11 @@ export function formatTerminalOutput(result: ScanResult, options: {
129
233
  output += c(colors.bold, ' OCULUM SECURITY SCAN RESULTS') + '\n'
130
234
  output += c(colors.bold, '═'.repeat(60)) + '\n\n'
131
235
 
236
+ // Baseline diff summary (if present)
237
+ if (baselineDiff) {
238
+ output += formatDiffSummary(baselineDiff)
239
+ }
240
+
132
241
  // Status
133
242
  if (hasBlockingIssues) {
134
243
  const blocking = severityCounts.critical + severityCounts.high
@@ -157,7 +266,7 @@ export function formatTerminalOutput(result: ScanResult, options: {
157
266
  output += c(colors.red, 'These must be fixed before merging:') + '\n\n'
158
267
 
159
268
  for (const finding of blockingIssues.slice(0, 10)) {
160
- output += formatFinding(finding)
269
+ output += formatFinding(finding, { compact, verbose })
161
270
  output += '\n'
162
271
  }
163
272
 
@@ -182,7 +291,47 @@ export function formatTerminalOutput(result: ScanResult, options: {
182
291
  if (nonBlocking.length === 0 && blockingIssues.length > 0) continue
183
292
  }
184
293
 
185
- output += formatGroup(group, maxFindingsPerGroup)
294
+ output += formatGroup(group, { maxFindings: maxFindingsPerGroup, compact, verbose })
295
+ }
296
+
297
+ // Suppressed findings section (if any)
298
+ if (result.suppressedVulnerabilities && result.suppressedVulnerabilities.length > 0) {
299
+ output += '\n' + c(colors.dim, '─'.repeat(60)) + '\n'
300
+ output += c(colors.dim + colors.bold, 'SUPPRESSED FINDINGS') + '\n'
301
+ output += c(colors.dim, `${result.suppressedVulnerabilities.length} findings suppressed`) + '\n\n'
302
+
303
+ for (const suppressed of result.suppressedVulnerabilities.slice(0, 5)) {
304
+ const typeLabel = suppressed.suppressionType === 'inline' ? 'inline'
305
+ : suppressed.suppressionType === 'config-finding' ? 'config'
306
+ : 'rule'
307
+ output += c(colors.dim, ` ${suppressed.hash.slice(0, 8)} ${suppressed.filePath}:${suppressed.lineNumber}`) + '\n'
308
+ output += c(colors.dim, ` ${suppressed.title}`) + '\n'
309
+ output += c(colors.dim, ` [${typeLabel}] ${suppressed.suppressionReason}`) + '\n'
310
+ if (suppressed.expires) {
311
+ output += c(colors.dim, ` Expires: ${suppressed.expires}`) + '\n'
312
+ }
313
+ output += '\n'
314
+ }
315
+
316
+ if (result.suppressedVulnerabilities.length > 5) {
317
+ output += c(colors.dim, ` ... and ${result.suppressedVulnerabilities.length - 5} more suppressed\n`)
318
+ }
319
+ }
320
+
321
+ // Suppression stats (if any)
322
+ if (result.suppressionStats && (result.suppressionStats.inlineSuppressed > 0 ||
323
+ result.suppressionStats.configFindingSuppressed > 0 ||
324
+ result.suppressionStats.configRuleSuppressed > 0)) {
325
+ const stats = result.suppressionStats
326
+ const parts: string[] = []
327
+ if (stats.inlineSuppressed > 0) parts.push(`${stats.inlineSuppressed} inline`)
328
+ if (stats.configFindingSuppressed > 0) parts.push(`${stats.configFindingSuppressed} config`)
329
+ if (stats.configRuleSuppressed > 0) parts.push(`${stats.configRuleSuppressed} rule`)
330
+ if (stats.expired > 0) parts.push(`${stats.expired} expired`)
331
+
332
+ if (!result.suppressedVulnerabilities) {
333
+ output += '\n' + c(colors.dim, `Suppressed: ${parts.join(', ')}`) + '\n'
334
+ }
186
335
  }
187
336
 
188
337
  // Footer
@@ -192,6 +341,222 @@ export function formatTerminalOutput(result: ScanResult, options: {
192
341
  return output
193
342
  }
194
343
 
344
+ /**
345
+ * Compact summary options
346
+ */
347
+ export interface CompactSummaryOptions {
348
+ /** Number findings for reference with show command */
349
+ showNumbers?: boolean
350
+ /** Limit shown per severity (default: 5) */
351
+ maxPerSeverity?: number
352
+ /** Show "Run oculum show..." hint */
353
+ showHint?: boolean
354
+ /** Disable colors */
355
+ noColor?: boolean
356
+ }
357
+
358
+ /**
359
+ * Format compact summary grouped by severity
360
+ * Output format:
361
+ * HIGH (2)
362
+ * 1. SQL injection in src/api/users.ts:42
363
+ * 2. Missing auth in src/api/admin.ts:15
364
+ */
365
+ export function formatCompactSummary(
366
+ vulnerabilities: Vulnerability[],
367
+ options: CompactSummaryOptions = {}
368
+ ): string {
369
+ const {
370
+ showNumbers = true,
371
+ maxPerSeverity = 5,
372
+ showHint = true,
373
+ noColor = false,
374
+ } = options
375
+
376
+ if (vulnerabilities.length === 0) {
377
+ return noColor
378
+ ? 'No security issues found.'
379
+ : c(colors.green, '✓ No security issues found.')
380
+ }
381
+
382
+ // Group by severity
383
+ const bySeverity: Record<VulnerabilitySeverity, Vulnerability[]> = {
384
+ critical: [],
385
+ high: [],
386
+ medium: [],
387
+ low: [],
388
+ info: [],
389
+ }
390
+
391
+ for (const v of vulnerabilities) {
392
+ bySeverity[v.severity].push(v)
393
+ }
394
+
395
+ // Build output
396
+ let output = ''
397
+ let globalIndex = 1
398
+
399
+ const severityOrder: VulnerabilitySeverity[] = ['critical', 'high', 'medium', 'low', 'info']
400
+ const severityColors: Record<VulnerabilitySeverity, string> = {
401
+ critical: colors.bgRed + colors.white,
402
+ high: colors.red,
403
+ medium: colors.yellow,
404
+ low: colors.blue,
405
+ info: colors.gray,
406
+ }
407
+
408
+ for (const severity of severityOrder) {
409
+ const findings = bySeverity[severity]
410
+ if (findings.length === 0) continue
411
+
412
+ // Severity header
413
+ const label = severity.toUpperCase()
414
+ const header = noColor
415
+ ? `${label} (${findings.length})`
416
+ : c(severityColors[severity] + colors.bold, `${label} (${findings.length})`)
417
+ output += `\n ${header}\n`
418
+
419
+ // Show findings
420
+ const shown = findings.slice(0, maxPerSeverity)
421
+ for (const finding of shown) {
422
+ const num = showNumbers ? `${globalIndex}. ` : ''
423
+ const location = noColor
424
+ ? `${finding.filePath}:${finding.lineNumber}`
425
+ : c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
426
+
427
+ output += noColor
428
+ ? ` ${num}${finding.title} in ${location}\n`
429
+ : ` ${c(colors.dim, num)}${finding.title} ${c(colors.dim, 'in')} ${location}\n`
430
+
431
+ globalIndex++
432
+ }
433
+
434
+ // Show truncation notice
435
+ if (findings.length > maxPerSeverity) {
436
+ const more = findings.length - maxPerSeverity
437
+ const truncated = noColor
438
+ ? ` ... and ${more} more\n`
439
+ : c(colors.dim, ` ... and ${more} more\n`)
440
+ output += truncated
441
+ globalIndex += more // Increment for hidden findings
442
+ }
443
+ }
444
+
445
+ // Hint at bottom
446
+ if (showHint && vulnerabilities.length > 0) {
447
+ output += '\n'
448
+ output += noColor
449
+ ? "Run 'oculum show 1' for details · 'oculum fix' for suggestions\n"
450
+ : c(colors.dim, "Run 'oculum show 1' for details · 'oculum fix' for suggestions\n")
451
+ }
452
+
453
+ return output
454
+ }
455
+
456
+ /**
457
+ * Format a numbered finding list for the show command
458
+ * Returns findings with their numbers for reference
459
+ */
460
+ export function getNumberedFindings(vulnerabilities: Vulnerability[]): Array<{ number: number; finding: Vulnerability }> {
461
+ return vulnerabilities.map((finding, index) => ({
462
+ number: index + 1,
463
+ finding,
464
+ }))
465
+ }
466
+
467
+ /**
468
+ * Format a single finding detail view for the show command
469
+ */
470
+ export function formatFindingDetail(
471
+ finding: Vulnerability,
472
+ number: number,
473
+ options: { verbose?: boolean; noColor?: boolean } = {}
474
+ ): string {
475
+ const { verbose = false, noColor = false } = options
476
+
477
+ let output = ''
478
+
479
+ // Header
480
+ const badge = noColor
481
+ ? `[${finding.severity.toUpperCase()}]`
482
+ : severityBadge(finding.severity)
483
+ const title = noColor ? finding.title : c(colors.bold, finding.title)
484
+ output += `\n#${number} ${badge} ${title}\n`
485
+
486
+ // Location
487
+ const location = noColor
488
+ ? finding.filePath + ':' + finding.lineNumber
489
+ : c(colors.cyan, `${finding.filePath}:${finding.lineNumber}`)
490
+ output += ` ${location}\n`
491
+ output += '\n'
492
+
493
+ // Impact
494
+ if (finding.impact) {
495
+ const impactLabel = noColor ? 'Impact:' : c(colors.yellow + colors.bold, 'Impact:')
496
+ output += ` ${impactLabel} ${finding.impact}\n`
497
+ output += '\n'
498
+ }
499
+
500
+ // Code snippet
501
+ if (finding.lineContent && finding.lineContent.trim()) {
502
+ const codeLabel = noColor ? 'Code:' : c(colors.dim, 'Code:')
503
+ const code = finding.lineContent.trim().substring(0, 100)
504
+ const codeText = noColor ? code : c(colors.white, code)
505
+ output += ` ${codeLabel} ${codeText}${finding.lineContent.trim().length > 100 ? '...' : ''}\n`
506
+ output += '\n'
507
+ }
508
+
509
+ // Description
510
+ output += noColor
511
+ ? ` ${finding.description}\n`
512
+ : ` ${c(colors.dim, finding.description)}\n`
513
+ output += '\n'
514
+
515
+ // Fix steps
516
+ if (finding.fixSteps && finding.fixSteps.length > 0) {
517
+ const fixLabel = noColor ? 'How to fix:' : c(colors.green + colors.bold, 'How to fix:')
518
+ output += ` ${fixLabel}\n`
519
+ finding.fixSteps.forEach((step, i) => {
520
+ const stepText = noColor ? `${i + 1}. ${step}` : c(colors.green, `${i + 1}. ${step}`)
521
+ output += ` ${stepText}\n`
522
+ })
523
+ output += '\n'
524
+ } else if (finding.suggestedFix) {
525
+ const fixLabel = noColor ? 'Suggested fix:' : c(colors.green + colors.bold, 'Suggested fix:')
526
+ output += ` ${fixLabel} ${finding.suggestedFix}\n`
527
+ output += '\n'
528
+ }
529
+
530
+ // Verbose mode: additional details
531
+ if (verbose) {
532
+ // References
533
+ if (finding.references && finding.references.length > 0) {
534
+ const refLabel = noColor ? 'References:' : c(colors.blue, 'References:')
535
+ output += ` ${refLabel}\n`
536
+ finding.references.forEach(ref => {
537
+ output += noColor
538
+ ? ` - ${ref}\n`
539
+ : ` ${c(colors.blue, `- ${ref}`)}\n`
540
+ })
541
+ output += '\n'
542
+ }
543
+
544
+ // Validation notes
545
+ if (finding.validationNotes) {
546
+ const notesLabel = noColor ? '[AI]' : c(colors.magenta, '[AI]')
547
+ output += ` ${notesLabel} ${finding.validationNotes}\n`
548
+ output += '\n'
549
+ }
550
+
551
+ // Category and confidence
552
+ output += noColor
553
+ ? ` Category: ${finding.category} · Confidence: ${finding.confidence || 'medium'} · Layer: ${finding.layer}\n`
554
+ : c(colors.dim, ` Category: ${finding.category} · Confidence: ${finding.confidence || 'medium'} · Layer: ${finding.layer}\n`)
555
+ }
556
+
557
+ return output
558
+ }
559
+
195
560
  /**
196
561
  * Format as simple list (no grouping, no colors)
197
562
  */
@@ -317,6 +682,76 @@ const RULE_METADATA: Record<string, { name: string; description: string; helpUri
317
682
  * For integration with GitHub Code Scanning
318
683
  */
319
684
  export function formatSARIF(result: ScanResult): object {
685
+ // Build results from active vulnerabilities
686
+ const activeResults = result.vulnerabilities.map((v, index) => ({
687
+ ruleId: v.category,
688
+ ruleIndex: getRuleIndex(result.vulnerabilities, v.category),
689
+ level: mapSeverityToSARIF(v.severity),
690
+ message: {
691
+ text: v.description,
692
+ },
693
+ locations: [{
694
+ physicalLocation: {
695
+ artifactLocation: {
696
+ uri: v.filePath,
697
+ uriBaseId: '%SRCROOT%',
698
+ },
699
+ region: {
700
+ startLine: v.lineNumber,
701
+ startColumn: 1,
702
+ snippet: v.lineContent ? { text: v.lineContent } : undefined,
703
+ },
704
+ },
705
+ }],
706
+ fingerprints: {
707
+ 'oculum/v1': `${v.category}:${v.filePath}:${v.lineNumber}`,
708
+ },
709
+ fixes: v.suggestedFix ? [{
710
+ description: {
711
+ text: v.suggestedFix,
712
+ },
713
+ }] : undefined,
714
+ properties: {
715
+ confidence: v.confidence,
716
+ layer: v.layer,
717
+ },
718
+ }))
719
+
720
+ // Build results from suppressed vulnerabilities (with SARIF suppression state)
721
+ const suppressedResults = (result.suppressedVulnerabilities || []).map((s) => ({
722
+ ruleId: s.category,
723
+ ruleIndex: 0, // Will be resolved by GitHub
724
+ level: mapSeverityToSARIF(s.severity),
725
+ message: {
726
+ text: s.title,
727
+ },
728
+ locations: [{
729
+ physicalLocation: {
730
+ artifactLocation: {
731
+ uri: s.filePath,
732
+ uriBaseId: '%SRCROOT%',
733
+ },
734
+ region: {
735
+ startLine: s.lineNumber,
736
+ startColumn: 1,
737
+ },
738
+ },
739
+ }],
740
+ fingerprints: {
741
+ 'oculum/v1': `${s.category}:${s.filePath}:${s.lineNumber}`,
742
+ 'oculum/hash': s.hash,
743
+ },
744
+ suppressions: [{
745
+ kind: s.suppressionType === 'inline' ? 'inSource' : 'external',
746
+ justification: s.suppressionReason,
747
+ state: 'accepted',
748
+ }],
749
+ properties: {
750
+ suppressionType: s.suppressionType,
751
+ expires: s.expires,
752
+ },
753
+ }))
754
+
320
755
  return {
321
756
  $schema: 'https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json',
322
757
  version: '2.1.0',
@@ -330,39 +765,7 @@ export function formatSARIF(result: ScanResult): object {
330
765
  rules: getUniqueRules(result.vulnerabilities),
331
766
  },
332
767
  },
333
- results: result.vulnerabilities.map((v, index) => ({
334
- ruleId: v.category,
335
- ruleIndex: getRuleIndex(result.vulnerabilities, v.category),
336
- level: mapSeverityToSARIF(v.severity),
337
- message: {
338
- text: v.description,
339
- },
340
- locations: [{
341
- physicalLocation: {
342
- artifactLocation: {
343
- uri: v.filePath,
344
- uriBaseId: '%SRCROOT%',
345
- },
346
- region: {
347
- startLine: v.lineNumber,
348
- startColumn: 1,
349
- snippet: v.lineContent ? { text: v.lineContent } : undefined,
350
- },
351
- },
352
- }],
353
- fingerprints: {
354
- 'oculum/v1': `${v.category}:${v.filePath}:${v.lineNumber}`,
355
- },
356
- fixes: v.suggestedFix ? [{
357
- description: {
358
- text: v.suggestedFix,
359
- },
360
- }] : undefined,
361
- properties: {
362
- confidence: v.confidence,
363
- layer: v.layer,
364
- },
365
- })),
768
+ results: [...activeResults, ...suppressedResults],
366
769
  columnKind: 'utf16CodeUnits',
367
770
  }],
368
771
  }