corgea-cli 1.9.3__tar.gz → 1.10.0__tar.gz

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 (178) hide show
  1. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.github/workflows/test.yml +3 -1
  2. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/Cargo.lock +1 -1
  3. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/Cargo.toml +1 -1
  4. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/PKG-INFO +1 -1
  5. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/skills/corgea/SKILL.md +14 -2
  6. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/config.rs +8 -0
  7. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/ecosystems/maven.rs +180 -2
  8. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/ecosystems/mod.rs +10 -0
  9. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/mod.rs +7 -0
  10. corgea_cli-1.10.0/src/deps/report.rs +261 -0
  11. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/run.rs +3 -6
  12. corgea_cli-1.10.0/src/deps/tests/maven_tests.rs +338 -0
  13. corgea_cli-1.10.0/src/deps/tests/report_tests.rs +218 -0
  14. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/list.rs +126 -48
  15. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/main.rs +200 -23
  16. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/mod.rs +34 -12
  17. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/test_support.rs +2 -2
  18. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/verdict.rs +1 -1
  19. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scan.rs +24 -4
  20. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/blast.rs +437 -9
  21. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/utils/api.rs +937 -20
  22. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/utils/generic.rs +145 -36
  23. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/vuln_api_stub/mod.rs +135 -8
  24. corgea_cli-1.10.0/src/wait.rs +171 -0
  25. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_deps.rs +99 -0
  26. corgea_cli-1.10.0/tests/cli_scan_sbom.rs +219 -0
  27. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_verdict.rs +51 -0
  28. corgea_cli-1.10.0/tests/cloud_commands_e2e/common/mod.rs +842 -0
  29. corgea_cli-1.10.0/tests/cloud_commands_e2e/inspect.rs +140 -0
  30. corgea_cli-1.10.0/tests/cloud_commands_e2e/main.rs +7 -0
  31. corgea_cli-1.10.0/tests/cloud_commands_e2e/scan_list.rs +148 -0
  32. corgea_cli-1.10.0/tests/cloud_commands_e2e/upload_wait.rs +149 -0
  33. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/common/mod.rs +197 -1
  34. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/README.md +1 -0
  35. corgea_cli-1.10.0/tests/fixtures/java-maven-alias/pom.xml +19 -0
  36. corgea_cli-1.10.0/tests/fixtures/java-maven-chained/pom.xml +20 -0
  37. corgea_cli-1.10.0/tests/fixtures/java-maven-deep-chain/pom.xml +25 -0
  38. corgea_cli-1.10.0/tests/fixtures/java-maven-managed/pom.xml +29 -0
  39. corgea_cli-1.10.0/tests/fixtures/java-maven-parent/pom.xml +29 -0
  40. corgea_cli-1.10.0/tests/fixtures/java-maven-plugin-deps/pom.xml +62 -0
  41. corgea_cli-1.10.0/tests/fixtures/java-maven-profile-mgmt/pom.xml +37 -0
  42. corgea_cli-1.10.0/tests/fixtures/java-maven-props/pom.xml +27 -0
  43. corgea_cli-1.10.0/tests/fixtures/java-maven-revision/pom.xml +19 -0
  44. corgea_cli-1.10.0/tests/fixtures/mixed-monorepo/services/api/requirements.txt +2 -0
  45. corgea_cli-1.10.0/tests/fixtures/mixed-monorepo/services/web/package-lock.json +29 -0
  46. corgea_cli-1.10.0/tests/fixtures/mixed-monorepo/services/web/package.json +10 -0
  47. corgea_cli-1.10.0/tests/list_code_quality.rs +209 -0
  48. corgea_cli-1.10.0/tests/list_resolution.rs +374 -0
  49. corgea_cli-1.10.0/tests/wait_resolution.rs +358 -0
  50. corgea_cli-1.9.3/.github/workflows/sync-skills.yml +0 -166
  51. corgea_cli-1.9.3/.github/workflows/update_docs..yml +0 -27
  52. corgea_cli-1.9.3/src/deps/report.rs +0 -172
  53. corgea_cli-1.9.3/src/deps/tests/maven_tests.rs +0 -129
  54. corgea_cli-1.9.3/src/deps/tests/report_tests.rs +0 -81
  55. corgea_cli-1.9.3/src/wait.rs +0 -102
  56. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.codex/hooks.json +0 -0
  57. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.github/workflows/npm-publish.yml +0 -0
  58. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.github/workflows/release-binaries.yml +0 -0
  59. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.github/workflows/release.yml +0 -0
  60. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.github/workflows/staging-contract.yml +0 -0
  61. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/.gitignore +0 -0
  62. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/AGENTS.md +0 -0
  63. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/LICENSE +0 -0
  64. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/README.md +0 -0
  65. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/RELEASING.md +0 -0
  66. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/bin/corgea.js +0 -0
  67. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/build_release.sh +0 -0
  68. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/examples/deps_skill.rs +0 -0
  69. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/harness +0 -0
  70. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/package.json +0 -0
  71. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/pyproject.toml +0 -0
  72. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/scripts/check-linux-binary.sh +0 -0
  73. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/scripts/npm/bundle-binaries.js +0 -0
  74. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/advisories.rs +0 -0
  75. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/authorize.rs +0 -0
  76. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/cicd.rs +0 -0
  77. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/catalog.rs +0 -0
  78. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/detect.rs +0 -0
  79. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/diff.rs +0 -0
  80. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/ecosystems/evaluate.rs +0 -0
  81. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/ecosystems/npm.rs +0 -0
  82. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/ecosystems/pypi.rs +0 -0
  83. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/explain.rs +0 -0
  84. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/findings.rs +0 -0
  85. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/model.rs +0 -0
  86. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/parse/mod.rs +0 -0
  87. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/parse/npm_lock.rs +0 -0
  88. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/parse/python_lock.rs +0 -0
  89. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/policy.rs +0 -0
  90. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/skill.rs +0 -0
  91. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/common.rs +0 -0
  92. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/correctness_tests.rs +0 -0
  93. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/detect_tests.rs +0 -0
  94. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/diff_tests.rs +0 -0
  95. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/explain_tests.rs +0 -0
  96. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/findings_tests.rs +0 -0
  97. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/mod.rs +0 -0
  98. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/npm_tests.rs +0 -0
  99. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/policy_tests.rs +0 -0
  100. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/pypi_tests.rs +0 -0
  101. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/robustness_tests.rs +0 -0
  102. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/deps/tests/slice0_tests.rs +0 -0
  103. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/inspect.rs +0 -0
  104. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/lib.rs +0 -0
  105. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/log.rs +0 -0
  106. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/detect.rs +0 -0
  107. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/exec.rs +0 -0
  108. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/parse.rs +0 -0
  109. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/render.rs +0 -0
  110. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/tree.rs +0 -0
  111. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/precheck/uv.rs +0 -0
  112. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/fortify.rs +0 -0
  113. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/parsers/checkmarx.rs +0 -0
  114. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/parsers/coverity.rs +0 -0
  115. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/parsers/mod.rs +0 -0
  116. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/parsers/sarif.rs +0 -0
  117. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/scanners/parsers/semgrep.rs +0 -0
  118. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/setup_hooks.rs +0 -0
  119. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/skill.rs +0 -0
  120. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/targets.rs +0 -0
  121. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/utils/terminal.rs +0 -0
  122. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/verify_deps/mod.rs +0 -0
  123. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/verify_deps/registry.rs +0 -0
  124. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/src/vuln_api/mod.rs +0 -0
  125. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_advisories.rs +0 -0
  126. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_bare_install.rs +0 -0
  127. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_deps_skill.rs +0 -0
  128. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_exec_fallback.rs +0 -0
  129. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_install.rs +0 -0
  130. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_npm_ci.rs +0 -0
  131. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_provenance.rs +0 -0
  132. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_remediation.rs +0 -0
  133. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_tree.rs +0 -0
  134. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/cli_uv_sync.rs +0 -0
  135. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/go-mod-smoke/go.mod +0 -0
  136. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/go-mod-smoke/go.sum +0 -0
  137. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/java-gradle/build.gradle +0 -0
  138. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/java-gradle/gradle.lockfile +0 -0
  139. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/java-maven/pom.xml +0 -0
  140. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/not-xml-pom.xml +0 -0
  141. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/package-lock.json +0 -0
  142. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/package.json +0 -0
  143. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/poetry.lock +0 -0
  144. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/pyproject.toml +0 -0
  145. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/malformed/truncated-poetry.lock +0 -0
  146. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-app/.corgea/deps.yml +0 -0
  147. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-app/package-lock.json +0 -0
  148. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-app/package.json +0 -0
  149. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-monorepo/package-lock.json +0 -0
  150. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-monorepo/package.json +0 -0
  151. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-monorepo/packages/a/package.json +0 -0
  152. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-monorepo/packages/b/package.json +0 -0
  153. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-pnpm/package.json +0 -0
  154. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-pnpm/pnpm-lock.yaml +0 -0
  155. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-stale/package-lock.json +0 -0
  156. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-stale/package.json +0 -0
  157. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-transitive/package-lock.json +0 -0
  158. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-transitive/package.json +0 -0
  159. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-yarn/package.json +0 -0
  160. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/node-yarn/yarn.lock +0 -0
  161. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-pip-nolock/requirements.txt +0 -0
  162. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-poetry/poetry.lock +0 -0
  163. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-poetry/pyproject.toml +0 -0
  164. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-poetry-multi/poetry.lock +0 -0
  165. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-poetry-multi/pyproject.toml +0 -0
  166. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-uv-requirements/pyproject.toml +0 -0
  167. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-uv-requirements/requirements.txt +0 -0
  168. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/python-uv-requirements/uv.lock +0 -0
  169. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/README.md +0 -0
  170. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/check_clean.json +0 -0
  171. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/check_malware.json +0 -0
  172. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/check_unknown.json +0 -0
  173. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/check_vulnerable.json +0 -0
  174. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/package_profile.json +0 -0
  175. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/fixtures/vuln_api/package_profile_not_found.json +0 -0
  176. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/harness_smoke.rs +0 -0
  177. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/logging_e2e.rs +0 -0
  178. {corgea_cli-1.9.3 → corgea_cli-1.10.0}/tests/vuln_api_contract.rs +0 -0
@@ -4,9 +4,11 @@ on:
4
4
  push:
5
5
  branches:
6
6
  - main
7
- - master
8
7
  pull_request:
9
8
 
9
+ permissions:
10
+ contents: read
11
+
10
12
  jobs:
11
13
  rust-tests:
12
14
  runs-on: ubuntu-latest
@@ -369,7 +369,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
369
369
 
370
370
  [[package]]
371
371
  name = "corgea"
372
- version = "1.9.3"
372
+ version = "1.10.0"
373
373
  dependencies = [
374
374
  "chrono",
375
375
  "clap",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "corgea"
3
- version = "1.9.3"
3
+ version = "1.10.0"
4
4
  edition = "2021"
5
5
  readme = "README.md"
6
6
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: corgea-cli
3
- Version: 1.9.3
3
+ Version: 1.10.0
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -35,8 +35,12 @@ corgea scan --scan-type policy --policy 1 # Specific policy ID
35
35
  corgea scan --fail-on CR # Exit 1 on critical issues (CR, HI, ME, LO)
36
36
  corgea scan --fail-on malicious # Exit 1 if any dependency is classified malicious
37
37
  corgea scan --fail-on HI,malicious # Comma-separated conditions combine
38
- corgea scan --fail # Exit 1 based on project blocking rules
38
+ corgea scan --block-on criticals # Exit 1 if the scan violates the named CI blocking rules
39
+ corgea scan --block-on criticals,malicious-deps # Comma-separated rule slugs
40
+ corgea scan --fail # Deprecated: exit 1 based on every active blocking rule
39
41
  corgea scan --out-format json --out-file r.json # Export (json, html, sarif, markdown)
42
+ corgea scan --sbom # Also write a CycloneDX SBOM to bom.json
43
+ corgea scan --sbom sbom.cdx.json # SBOM to a custom file
40
44
  corgea scan --project-name my-service # Override project name
41
45
  ```
42
46
 
@@ -44,7 +48,11 @@ Scan types: `blast` (base AI), `policy` (PolicyIQ), `malicious`, `secrets`, `pii
44
48
 
45
49
  `--fail-on` takes comma-separated conditions: severity thresholds `CR`, `HI`, `ME`, `LO` (trip at or above the level) and/or `malicious` (trips when any dependency in the scan is classified malicious). Use `malicious` to block supply-chain findings in CI across every ecosystem the scan covers, including those the `corgea npm`/`corgea pip` install gate does not.
46
50
 
47
- `--only-uncommitted` and `--target` are mutually exclusive. `--fail-on` and `--fail` are mutually exclusive.
51
+ `--block-on` takes comma-separated blocking-rule slugs. Blocking rules are configured in the web app and each one applies either to pull requests or to CI. Only CI rules can be named by `--block-on`; a slug that is unknown, inactive, or scoped to pull requests is a hard error (exit 1) rather than a silently skipped gate. The slug is shown next to each rule in the web app and is derived from the rule name, so renaming a rule changes its slug. Name rules for the condition that trips them — `criticals`, not `no-criticals` — so that `--block-on` reads as a direct assertion rather than a double negative.
52
+
53
+ `--fail` is deprecated. It evaluates every active blocking rule regardless of what it applies to; use `--block-on` to name the CI rules a pipeline should enforce.
54
+
55
+ `--only-uncommitted` and `--target` are mutually exclusive. `--fail-on`, `--fail`, and `--block-on` are mutually exclusive.
48
56
 
49
57
  ### Upload — `corgea upload [report]`
50
58
 
@@ -75,6 +83,7 @@ corgea wait --scan-id SCAN_ID # Wait for specific scan
75
83
  corgea ls # List scans
76
84
  corgea ls --issues --scan-id SCAN_ID # Issues for a scan
77
85
  corgea ls --sca-issues # SCA (dependency) issues
86
+ corgea ls --code-quality # Code quality issues
78
87
  corgea ls --issues --page 2 --page-size 10 # Pagination
79
88
  corgea ls --issues --scan-id SCAN_ID --json # JSON output
80
89
  ```
@@ -83,6 +92,7 @@ corgea ls --issues --scan-id SCAN_ID --json # JSON output
83
92
  |------|-------|-------------|
84
93
  | `--issues` | `-i` | List code/SAST issues |
85
94
  | `--sca-issues` | `-c` | List SCA issues |
95
+ | `--code-quality` | `-q` | List code quality issues (alias `--quality`) |
86
96
  | `--scan-id` | `-s` | Filter to a scan |
87
97
  | `--page` | `-p` | Page number |
88
98
  | `--page-size` | | Items per page |
@@ -354,6 +364,7 @@ corgea inspect --issue --diff ISSUE_ID
354
364
  ```bash
355
365
  corgea scan --fail-on CR --out-format sarif --out-file results.sarif
356
366
  corgea scan --fail-on CR,malicious --out-format sarif --out-file results.sarif # also block malicious dependencies
367
+ corgea scan --block-on criticals --out-format sarif --out-file results.sarif # gate on a CI blocking rule from the web app
357
368
  ```
358
369
 
359
370
  ### Upload third-party reports
@@ -367,6 +378,7 @@ corgea upload report.json --project-name my-app
367
378
  ```bash
368
379
  corgea scan --out-format html --out-file report.html
369
380
  corgea scan --out-format sarif --out-file report.sarif
381
+ corgea scan --out-format sarif --out-file report.sarif --sbom bom.json # SARIF + CycloneDX SBOM
370
382
  ```
371
383
 
372
384
  ## Severity Levels
@@ -4,6 +4,14 @@ use std::{env, fs, io};
4
4
 
5
5
  pub const DEFAULT_VULN_API_URL: &str = "https://cve-worker-staging.corgea.workers.dev";
6
6
 
7
+ /// Whether `DEFAULT_VULN_API_URL` is an endpoint Corgea tokens belong to.
8
+ /// The `-staging` hostname is historical: this worker serves as the production
9
+ /// vuln-api, so a token may be sent to it and authenticated (fail-closed)
10
+ /// verdicts apply by default. Set to `false` if the default is ever repointed
11
+ /// at an endpoint that customer tokens should not reach — token-send and
12
+ /// fail-closed then both require the explicit opt-in.
13
+ pub const DEFAULT_VULN_API_URL_IS_PRODUCTION: bool = true;
14
+
7
15
  #[derive(Serialize, Deserialize, Clone)]
8
16
  pub struct Config {
9
17
  pub(crate) url: String,
@@ -5,7 +5,7 @@ use crate::deps::ecosystems::classify_constraint;
5
5
  use crate::deps::ecosystems::evaluate::{
6
6
  constraint_to_findings, dep001, file_in_dir, parent_dir, ScanContext,
7
7
  };
8
- use crate::deps::model::{DependencyNode, Ecosystem, PackageId, Scope, SourceType};
8
+ use crate::deps::model::{DependencyEdge, DependencyNode, Ecosystem, PackageId, Scope, SourceType};
9
9
  use crate::deps::DepsError;
10
10
 
11
11
  pub fn scan_maven_projects(ctx: &mut ScanContext<'_>) -> Result<(), DepsError> {
@@ -68,6 +68,14 @@ fn scan_maven_pom(ctx: &mut ScanContext<'_>, dir: &Path, pom_path: &Path) -> Res
68
68
  Some(package_id.clone()),
69
69
  false,
70
70
  ));
71
+ ctx.graph.edges.push(DependencyEdge {
72
+ from: PackageId::root(),
73
+ to: package_id.clone(),
74
+ declared_constraint: declared.clone(),
75
+ resolved_version: Some(dep.version.clone()),
76
+ scope: dep.scope,
77
+ source_file: rel.clone(),
78
+ });
71
79
  ctx.graph.nodes.push(DependencyNode {
72
80
  id: package_id,
73
81
  name,
@@ -89,8 +97,178 @@ fn scan_maven_pom(ctx: &mut ScanContext<'_>, dir: &Path, pom_path: &Path) -> Res
89
97
  Ok(())
90
98
  }
91
99
 
100
+ /// Sections whose contents are not the project's own coordinates or
101
+ /// dependencies; stripped before any dependency or property extraction.
102
+ const NON_DEPENDENCY_SECTIONS: &[&str] = &["profiles", "build", "reporting"];
103
+
92
104
  fn parse_pom_dependencies(content: &str) -> Result<Vec<MavenDep>, DepsError> {
93
- Ok(parse_pom_regex(content))
105
+ let stripped = strip_sections(content, NON_DEPENDENCY_SECTIONS);
106
+ let props = parse_pom_properties(&stripped);
107
+ let (rest, management) = split_dependency_management(&stripped);
108
+ let managed: std::collections::HashMap<(String, String), String> = parse_pom_regex(management)
109
+ .into_iter()
110
+ .filter(|d| !d.version.is_empty())
111
+ .map(|d| ((d.group, d.artifact), d.version))
112
+ .collect();
113
+ let mut deps = parse_pom_regex(&rest);
114
+ for dep in &mut deps {
115
+ if dep.version.is_empty() {
116
+ if let Some(v) = managed.get(&(dep.group.clone(), dep.artifact.clone())) {
117
+ dep.version = v.clone();
118
+ }
119
+ }
120
+ dep.version = resolve_placeholders(&dep.version, &props);
121
+ }
122
+ Ok(deps)
123
+ }
124
+
125
+ /// Remove every `<tag>...</tag>` section. Their dependency blocks are not
126
+ /// application dependencies, and a profile's `<properties>` or
127
+ /// `<dependencyManagement>` must not affect the base dependency graph, so
128
+ /// stripping runs before property and management extraction.
129
+ fn strip_sections(content: &str, tags: &[&str]) -> String {
130
+ let mut content = content.to_string();
131
+ for tag in tags {
132
+ while let Some((rest, _)) = split_section(&content, tag) {
133
+ content = rest;
134
+ }
135
+ }
136
+ content
137
+ }
138
+
139
+ /// Split out the `<dependencyManagement>` section: its entries pin versions
140
+ /// for the project's dependencies but are not dependencies themselves.
141
+ /// Returns (pom without the section, the section's inner content).
142
+ fn split_dependency_management(content: &str) -> (String, &str) {
143
+ split_section(content, "dependencyManagement").unwrap_or_else(|| (content.to_string(), ""))
144
+ }
145
+
146
+ /// Locate a `<tag>...</tag>` section and split content into (content with
147
+ /// the section removed, the section's inner content). `None` if the tag
148
+ /// isn't present or is malformed (close before open).
149
+ fn split_section<'a>(content: &'a str, tag: &str) -> Option<(String, &'a str)> {
150
+ let open = format!("<{tag}>");
151
+ let close = format!("</{tag}>");
152
+ let s = content.find(&open)?;
153
+ let e = content.find(&close)?;
154
+ if s >= e {
155
+ return None;
156
+ }
157
+ let inner = &content[s + open.len()..e];
158
+ let rest = format!("{}{}", &content[..s], &content[e + close.len()..]);
159
+ Some((rest, inner))
160
+ }
161
+
162
+ /// Collect `<properties>` entries plus the built-in `project.version`.
163
+ fn parse_pom_properties(content: &str) -> std::collections::HashMap<String, String> {
164
+ let mut props = std::collections::HashMap::new();
165
+ if let Some(start) = content.find("<properties>") {
166
+ let rest = &content[start + "<properties>".len()..];
167
+ if let Some(end) = rest.find("</properties>") {
168
+ let mut block = &rest[..end];
169
+ while let Some(open_start) = block.find('<') {
170
+ let after = &block[open_start + 1..];
171
+ let Some(open_end) = after.find('>') else {
172
+ break;
173
+ };
174
+ let tag = after[..open_end].trim();
175
+ let body = &after[open_end + 1..];
176
+ if tag.starts_with('/') || tag.starts_with('!') || tag.ends_with('/') {
177
+ block = body;
178
+ continue;
179
+ }
180
+ let close = format!("</{tag}>");
181
+ match body.find(&close) {
182
+ Some(close_pos) => {
183
+ props.insert(tag.to_string(), body[..close_pos].trim().to_string());
184
+ block = &body[close_pos + close.len()..];
185
+ }
186
+ None => block = body,
187
+ }
188
+ }
189
+ }
190
+ }
191
+ // Property values may reference other properties; resolve the map to a
192
+ // fixed point, bounded to guard against definition cycles.
193
+ resolve_props_fixed_point(&mut props);
194
+ let project_version = resolve_placeholders(&pom_project_version(content), &props);
195
+ if !project_version.is_empty() && !project_version.contains("${") {
196
+ props.insert("project.version".to_string(), project_version);
197
+ }
198
+ // Properties aliasing ${project.version} (e.g. `<shared.version>`) only
199
+ // resolve now that project.version itself is in the map.
200
+ resolve_props_fixed_point(&mut props);
201
+ props
202
+ }
203
+
204
+ /// Resolve `${...}` references among property values to a fixed point. A
205
+ /// resolution chain can't be longer than the number of properties without
206
+ /// a cycle, so bound the iteration count by the map size.
207
+ fn resolve_props_fixed_point(props: &mut std::collections::HashMap<String, String>) {
208
+ for _ in 0..=props.len() {
209
+ let resolved: std::collections::HashMap<String, String> = props
210
+ .iter()
211
+ .map(|(k, v)| (k.clone(), resolve_placeholders(v, props)))
212
+ .collect();
213
+ if resolved == *props {
214
+ break;
215
+ }
216
+ *props = resolved;
217
+ }
218
+ }
219
+
220
+ /// The pom's own `<version>`: first `<version>` before `<dependencies>`,
221
+ /// excluding the `<parent>` block. A child that inherits its version has
222
+ /// none of its own, so fall back to the parent's (Maven's inheritance rule).
223
+ fn pom_project_version(content: &str) -> String {
224
+ let head = content.split("<dependencies>").next().unwrap_or(content);
225
+ // The project's own <version> lives among its coordinates, before any
226
+ // nested section that may carry unrelated <version> tags of its own
227
+ // (plugin versions in <build>, managed versions in
228
+ // <dependencyManagement>, etc).
229
+ let nested_start = NON_DEPENDENCY_SECTIONS
230
+ .iter()
231
+ .copied()
232
+ .chain(["dependencyManagement"])
233
+ .filter_map(|tag| head.find(&format!("<{tag}>")))
234
+ .min();
235
+ let head = match nested_start {
236
+ Some(pos) => &head[..pos],
237
+ None => head,
238
+ };
239
+ if let Some((cleaned, parent)) = split_section(head, "parent") {
240
+ let own = extract_xml_tag(&cleaned, "version");
241
+ if !own.is_empty() {
242
+ return own;
243
+ }
244
+ return extract_xml_tag(parent, "version");
245
+ }
246
+ extract_xml_tag(head, "version")
247
+ }
248
+
249
+ /// Substitute `${name}` placeholders; unresolved ones pass through unchanged.
250
+ fn resolve_placeholders(raw: &str, props: &std::collections::HashMap<String, String>) -> String {
251
+ if !raw.contains("${") {
252
+ return raw.to_string();
253
+ }
254
+ let mut out = String::new();
255
+ let mut rest = raw;
256
+ while let Some(start) = rest.find("${") {
257
+ out.push_str(&rest[..start]);
258
+ let after = &rest[start + 2..];
259
+ let Some(end) = after.find('}') else {
260
+ out.push_str(&rest[start..]);
261
+ return out;
262
+ };
263
+ let key = &after[..end];
264
+ match props.get(key) {
265
+ Some(v) => out.push_str(v),
266
+ None => out.push_str(&rest[start..start + 2 + end + 1]),
267
+ }
268
+ rest = &after[end + 1..];
269
+ }
270
+ out.push_str(rest);
271
+ out
94
272
  }
95
273
 
96
274
  fn parse_pom_regex(content: &str) -> Vec<MavenDep> {
@@ -10,6 +10,16 @@ pub fn scan_all(ctx: &mut ScanContext<'_>) -> Result<(), DepsError> {
10
10
  evaluate::scan_all(ctx)
11
11
  }
12
12
 
13
+ /// Whether `scan_all` builds graph nodes for this ecosystem. Keep in sync
14
+ /// with the scanners wired in `evaluate::scan_all` — detect-only ecosystems
15
+ /// (Go, Cargo) are found on disk but contribute nothing to the graph or SBOM.
16
+ pub fn is_graphed(ecosystem: Ecosystem) -> bool {
17
+ matches!(
18
+ ecosystem,
19
+ Ecosystem::Npm | Ecosystem::PyPI | Ecosystem::Maven
20
+ )
21
+ }
22
+
13
23
  use crate::deps::model::{ConstraintKind, Ecosystem};
14
24
 
15
25
  /// Classify a raw declared constraint string.
@@ -62,6 +62,13 @@ impl Inventory {
62
62
 
63
63
  /// Scan a directory tree: detect files, build the graph, evaluate policy.
64
64
  pub fn scan(root: &Path, policy: &Policy) -> Result<Inventory, DepsError> {
65
+ if !root.exists() {
66
+ return Err(DepsError(format!(
67
+ "path does not exist: {}",
68
+ root.display()
69
+ )));
70
+ }
71
+
65
72
  let detected = detect::detect_dependency_files(root);
66
73
  let mut graph = DependencyGraph::default();
67
74
  let mut findings = Vec::new();
@@ -0,0 +1,261 @@
1
+ use serde_json::{json, Value};
2
+ use std::fmt::Write as _;
3
+
4
+ use crate::deps::detect::DetectedFile;
5
+ use crate::deps::model::{DependencyGraph, Ecosystem, PackageId};
6
+ use crate::deps::Inventory;
7
+
8
+ /// Load the policy at `root`, scan the tree, and emit a CycloneDX SBOM.
9
+ pub fn sbom(root: &std::path::Path) -> Result<Value, crate::deps::DepsError> {
10
+ let policy = crate::deps::run::load_policy(root)?;
11
+ let inv = crate::deps::scan(root, &policy)?;
12
+ warn_unsupported_ecosystems(&inv.detected_files);
13
+ Ok(to_cyclonedx(&inv))
14
+ }
15
+
16
+ /// Print one deduplicated stderr warning per detected ecosystem that
17
+ /// `to_cyclonedx` does not emit components for (currently Go and Cargo),
18
+ /// so `deps sbom` / `scan --sbom` don't silently produce an empty-looking
19
+ /// SBOM for those trees.
20
+ pub fn warn_unsupported_ecosystems(detected: &[DetectedFile]) {
21
+ let mut by_ecosystem: std::collections::BTreeMap<
22
+ &'static str,
23
+ std::collections::BTreeSet<String>,
24
+ > = std::collections::BTreeMap::new();
25
+
26
+ for f in detected {
27
+ let Some(label) = unsupported_ecosystem_label(f.ecosystem) else {
28
+ continue;
29
+ };
30
+ let file_name = f
31
+ .path
32
+ .file_name()
33
+ .map(|n| n.to_string_lossy().into_owned())
34
+ .unwrap_or_default();
35
+ by_ecosystem.entry(label).or_default().insert(file_name);
36
+ }
37
+
38
+ for (label, files) in by_ecosystem {
39
+ let files: Vec<String> = files.into_iter().collect();
40
+ eprintln!(
41
+ "Warning: detected {} but {} is not yet included in SBOMs",
42
+ files.join(", "),
43
+ label
44
+ );
45
+ }
46
+ }
47
+
48
+ fn unsupported_ecosystem_label(ecosystem: Ecosystem) -> Option<&'static str> {
49
+ if crate::deps::ecosystems::is_graphed(ecosystem) {
50
+ return None;
51
+ }
52
+ Some(match ecosystem {
53
+ Ecosystem::Go => "Go",
54
+ Ecosystem::Cargo => "Cargo",
55
+ _ => "this ecosystem",
56
+ })
57
+ }
58
+
59
+ pub fn to_json(inv: &Inventory) -> Value {
60
+ inventory_to_json(inv)
61
+ }
62
+
63
+ pub fn to_sarif(inv: &Inventory) -> Value {
64
+ let rules: Vec<Value> = inv
65
+ .findings
66
+ .iter()
67
+ .map(|f| {
68
+ json!({
69
+ "id": f.id,
70
+ "name": f.title,
71
+ "shortDescription": { "text": f.title },
72
+ })
73
+ })
74
+ .collect();
75
+
76
+ let results: Vec<Value> = inv
77
+ .findings
78
+ .iter()
79
+ .map(|f| {
80
+ json!({
81
+ "ruleId": f.id,
82
+ "level": severity_to_sarif(f.severity),
83
+ "message": { "text": f.recommendation },
84
+ })
85
+ })
86
+ .collect();
87
+
88
+ json!({
89
+ "version": "2.1.0",
90
+ "runs": [{
91
+ "tool": {
92
+ "driver": {
93
+ "name": "corgea-deps",
94
+ "rules": rules,
95
+ }
96
+ },
97
+ "results": results,
98
+ }]
99
+ })
100
+ }
101
+
102
+ fn severity_to_sarif(sev: crate::deps::model::Severity) -> &'static str {
103
+ use crate::deps::model::Severity;
104
+ match sev {
105
+ Severity::Critical | Severity::High => "error",
106
+ Severity::Medium => "warning",
107
+ Severity::Low | Severity::Info => "note",
108
+ }
109
+ }
110
+
111
+ /// A version is resolved when it names a concrete release — not empty, not
112
+ /// the `?` placeholder for lockfile-less manifests, not an unsubstituted
113
+ /// `${...}` Maven property. Unresolved versions (and the purls fabricated
114
+ /// from them) must be omitted, or the document fails CycloneDX 1.7 validation.
115
+ fn is_resolved_version(version: &str) -> bool {
116
+ !version.is_empty() && version != "?" && !version.contains("${")
117
+ }
118
+
119
+ pub fn to_cyclonedx(inv: &Inventory) -> Value {
120
+ let graph = &inv.graph;
121
+ // Deduplicate by bom-ref: multi-module trees list the same package once
122
+ // per manifest, but the schema sets uniqueItems on components.
123
+ let mut components: std::collections::BTreeMap<&str, Value> = std::collections::BTreeMap::new();
124
+ for n in graph.nodes.iter().filter(|n| *n.id() != PackageId::root()) {
125
+ let mut c = json!({
126
+ "type": "library",
127
+ "bom-ref": n.id().0,
128
+ "name": n.name(),
129
+ });
130
+ if let Some(v) = n.version().filter(|v| is_resolved_version(v)) {
131
+ c["version"] = json!(v);
132
+ c["purl"] = json!(n.id().0);
133
+ }
134
+ components.entry(&n.id().0).or_insert(c);
135
+ }
136
+ let components: Vec<Value> = components.into_values().collect();
137
+
138
+ let mut depends_on: std::collections::BTreeMap<&str, std::collections::BTreeSet<&str>> =
139
+ std::collections::BTreeMap::new();
140
+ for e in &graph.edges {
141
+ depends_on.entry(&e.from.0).or_default().insert(&e.to.0);
142
+ }
143
+ let deps: Vec<Value> = depends_on
144
+ .iter()
145
+ .map(|(from, tos)| {
146
+ json!({
147
+ "ref": from,
148
+ "dependsOn": tos,
149
+ })
150
+ })
151
+ .collect();
152
+
153
+ let root_name = std::fs::canonicalize(&inv.root)
154
+ .ok()
155
+ .and_then(|p| p.file_name().map(|n| n.to_string_lossy().into_owned()))
156
+ .unwrap_or_else(|| "project".to_string());
157
+
158
+ json!({
159
+ "bomFormat": "CycloneDX",
160
+ "specVersion": "1.7",
161
+ "serialNumber": format!("urn:uuid:{}", uuid::Uuid::new_v4()),
162
+ "version": 1,
163
+ "metadata": {
164
+ "timestamp": chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
165
+ "tools": {
166
+ "components": [{
167
+ "type": "application",
168
+ "name": "corgea",
169
+ "version": env!("CARGO_PKG_VERSION"),
170
+ }]
171
+ },
172
+ "component": {
173
+ "type": "application",
174
+ "bom-ref": "root",
175
+ "name": root_name,
176
+ },
177
+ },
178
+ "components": components,
179
+ "dependencies": deps,
180
+ })
181
+ }
182
+
183
+ pub fn graph_nodes_json(graph: &DependencyGraph) -> Vec<Value> {
184
+ graph
185
+ .nodes
186
+ .iter()
187
+ .map(|n| {
188
+ json!({
189
+ "id": n.id().0,
190
+ "name": n.name(),
191
+ "version": n.version(),
192
+ "direct": n.is_direct(),
193
+ "scope": format!("{:?}", n.scope()),
194
+ "depth": n.depth(),
195
+ })
196
+ })
197
+ .collect()
198
+ }
199
+
200
+ pub fn inventory_to_json(inv: &Inventory) -> Value {
201
+ let nodes = graph_nodes_json(&inv.graph);
202
+
203
+ let findings: Vec<Value> = inv
204
+ .findings
205
+ .iter()
206
+ .map(|f| {
207
+ json!({
208
+ "id": f.id,
209
+ "severity": format!("{:?}", f.severity),
210
+ "title": f.title,
211
+ "package": f.package.as_ref().map(|p| p.0.clone()),
212
+ "reproducible": f.reproducible,
213
+ "recommendation": f.recommendation,
214
+ })
215
+ })
216
+ .collect();
217
+
218
+ json!({
219
+ "root": inv.root,
220
+ "nodes": nodes,
221
+ "findings": findings,
222
+ })
223
+ }
224
+
225
+ pub fn table_output(inv: &Inventory) -> String {
226
+ let mut out = String::new();
227
+ writeln!(&mut out, "Corgea dependency inventory\n").unwrap();
228
+ writeln!(
229
+ &mut out,
230
+ "Detected {} dependency file(s)",
231
+ inv.detected_files.len()
232
+ )
233
+ .unwrap();
234
+ writeln!(
235
+ &mut out,
236
+ "Inventory: {} packages, {} findings\n",
237
+ inv.graph.nodes.len(),
238
+ inv.findings.len()
239
+ )
240
+ .unwrap();
241
+
242
+ let mut by_sev: std::collections::BTreeMap<String, usize> = std::collections::BTreeMap::new();
243
+ for f in &inv.findings {
244
+ *by_sev.entry(format!("{:?}", f.severity)).or_default() += 1;
245
+ }
246
+ for (sev, count) in by_sev {
247
+ writeln!(&mut out, " {sev}: {count}").unwrap();
248
+ }
249
+
250
+ for f in &inv.findings {
251
+ let pkg = f.package.as_ref().map(|p| p.name()).unwrap_or("project");
252
+ writeln!(&mut out, "\n {} {:?} {}", f.id, f.severity, f.title).unwrap();
253
+ writeln!(&mut out, " package: {pkg}").unwrap();
254
+ writeln!(&mut out, " {}", f.recommendation).unwrap();
255
+ }
256
+ out
257
+ }
258
+
259
+ pub fn print_table(inv: &Inventory) {
260
+ print!("{}", table_output(inv));
261
+ }
@@ -9,7 +9,7 @@ use serde_json::{json, Value};
9
9
  use crate::deps::findings::Finding;
10
10
  use crate::deps::model::{DependencyNode, Severity};
11
11
  use crate::deps::policy::Policy;
12
- use crate::deps::report::{graph_nodes_json, table_output, to_cyclonedx, to_json, to_sarif};
12
+ use crate::deps::report::{graph_nodes_json, table_output, to_json, to_sarif};
13
13
  use crate::deps::{scan, DepsError};
14
14
 
15
15
  #[derive(Subcommand, Debug, Clone)]
@@ -236,13 +236,10 @@ fn run_inner(sub: DepsSubcommand) -> Result<u8, DepsError> {
236
236
  Ok(0)
237
237
  }
238
238
  DepsSubcommand::Sbom { format, path, out } => {
239
- let root = Path::new(&path);
240
- let policy = load_policy(root)?;
241
- let inv = scan(root, &policy)?;
242
239
  if format != "cyclonedx" {
243
240
  return Err(DepsError(format!("unsupported SBOM format: {format}")));
244
241
  }
245
- let sbom = to_cyclonedx(&inv.graph).to_string();
242
+ let sbom = crate::deps::report::sbom(Path::new(&path))?.to_string();
246
243
  if let Some(out_path) = out {
247
244
  std::fs::write(&out_path, sbom)
248
245
  .map_err(|e| DepsError(format!("write sbom: {e}")))?;
@@ -728,7 +725,7 @@ fn shell_word(value: &str) -> String {
728
725
  format!("'{}'", value.replace('\'', "'\\''"))
729
726
  }
730
727
 
731
- fn load_policy(root: &Path) -> Result<Policy, DepsError> {
728
+ pub(crate) fn load_policy(root: &Path) -> Result<Policy, DepsError> {
732
729
  let policy_path = root.join(".corgea").join("deps.yml");
733
730
  if !policy_path.exists() {
734
731
  return Ok(Policy::default());