corgea-cli 1.14.0__tar.gz → 1.14.2__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 (186) hide show
  1. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/Cargo.lock +1 -1
  2. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/Cargo.toml +1 -1
  3. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/PKG-INFO +1 -1
  4. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/README.md +30 -14
  5. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/main.rs +1 -0
  6. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scan.rs +178 -46
  7. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/fortify.rs +69 -39
  8. corgea_cli-1.14.2/src/scanners/report_paths.rs +632 -0
  9. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/api.rs +452 -173
  10. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/main.rs +2 -1
  11. corgea_cli-1.14.0/tests/cloud_commands_e2e/gateway_retry.rs → corgea_cli-1.14.2/tests/cloud_commands_e2e/transient_retry.rs +146 -50
  12. corgea_cli-1.14.2/tests/cloud_commands_e2e/upload_report_paths.rs +168 -0
  13. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.codex/hooks.json +0 -0
  14. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/npm-publish.yml +0 -0
  15. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/release-binaries.yml +0 -0
  16. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/release.yml +0 -0
  17. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/staging-contract.yml +0 -0
  18. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/test.yml +0 -0
  19. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.gitignore +0 -0
  20. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/AGENTS.md +0 -0
  21. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/LICENSE +0 -0
  22. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/RELEASING.md +0 -0
  23. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/bin/corgea.js +0 -0
  24. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/build_release.sh +0 -0
  25. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/examples/deps_skill.rs +0 -0
  26. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/harness +0 -0
  27. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/package.json +0 -0
  28. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/pyproject.toml +0 -0
  29. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/scripts/check-linux-binary.sh +0 -0
  30. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/scripts/npm/bundle-binaries.js +0 -0
  31. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/skills/corgea/SKILL.md +0 -0
  32. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/advisories.rs +0 -0
  33. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/authorize.rs +0 -0
  34. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/cicd.rs +0 -0
  35. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/config.rs +0 -0
  36. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/catalog.rs +0 -0
  37. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/detect.rs +0 -0
  38. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/diff.rs +0 -0
  39. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/evaluate.rs +0 -0
  40. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/maven.rs +0 -0
  41. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/mod.rs +0 -0
  42. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/npm.rs +0 -0
  43. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/pypi.rs +0 -0
  44. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/explain.rs +0 -0
  45. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/findings.rs +0 -0
  46. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/mod.rs +0 -0
  47. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/model.rs +0 -0
  48. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/mod.rs +0 -0
  49. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/npm_lock.rs +0 -0
  50. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/python_lock.rs +0 -0
  51. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/policy.rs +0 -0
  52. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/report.rs +0 -0
  53. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/run.rs +0 -0
  54. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/skill.rs +0 -0
  55. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/common.rs +0 -0
  56. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/correctness_tests.rs +0 -0
  57. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/detect_tests.rs +0 -0
  58. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/diff_tests.rs +0 -0
  59. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/explain_tests.rs +0 -0
  60. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/findings_tests.rs +0 -0
  61. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/maven_tests.rs +0 -0
  62. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/mod.rs +0 -0
  63. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/npm_tests.rs +0 -0
  64. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/policy_tests.rs +0 -0
  65. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/pypi_tests.rs +0 -0
  66. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/report_tests.rs +0 -0
  67. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/robustness_tests.rs +0 -0
  68. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/slice0_tests.rs +0 -0
  69. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/images.rs +0 -0
  70. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/incremental.rs +0 -0
  71. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/inspect.rs +0 -0
  72. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/lib.rs +0 -0
  73. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/list.rs +0 -0
  74. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/log.rs +0 -0
  75. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/mcp.rs +0 -0
  76. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/detect.rs +0 -0
  77. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/exec.rs +0 -0
  78. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/mod.rs +0 -0
  79. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/parse.rs +0 -0
  80. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/render.rs +0 -0
  81. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/test_support.rs +0 -0
  82. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/tree.rs +0 -0
  83. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/uv.rs +0 -0
  84. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/verdict.rs +0 -0
  85. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/blast.rs +0 -0
  86. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/checkmarx.rs +0 -0
  87. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/coverity.rs +0 -0
  88. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/mod.rs +0 -0
  89. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/sarif.rs +0 -0
  90. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/semgrep.rs +0 -0
  91. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/setup_hooks.rs +0 -0
  92. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/skill.rs +0 -0
  93. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/skip_scan.rs +0 -0
  94. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/targets.rs +0 -0
  95. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/generic.rs +0 -0
  96. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/terminal.rs +0 -0
  97. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/verify_deps/mod.rs +0 -0
  98. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/verify_deps/registry.rs +0 -0
  99. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/vuln_api/mod.rs +0 -0
  100. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/vuln_api_stub/mod.rs +0 -0
  101. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/wait.rs +0 -0
  102. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_advisories.rs +0 -0
  103. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_bare_install.rs +0 -0
  104. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_config_errors.rs +0 -0
  105. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_deps.rs +0 -0
  106. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_deps_skill.rs +0 -0
  107. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_exec_fallback.rs +0 -0
  108. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_install.rs +0 -0
  109. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_mcp.rs +0 -0
  110. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_npm_ci.rs +0 -0
  111. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_provenance.rs +0 -0
  112. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_remediation.rs +0 -0
  113. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_include_image.rs +0 -0
  114. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_sbom.rs +0 -0
  115. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_wait_terminal_state.rs +0 -0
  116. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_tree.rs +0 -0
  117. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_uv_sync.rs +0 -0
  118. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_verdict.rs +0 -0
  119. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/block_on_report.rs +0 -0
  120. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/common/mod.rs +0 -0
  121. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/inspect.rs +0 -0
  122. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_incremental.rs +0 -0
  123. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_list.rs +0 -0
  124. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_skip.rs +0 -0
  125. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/upload_wait.rs +0 -0
  126. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/common/mod.rs +0 -0
  127. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/README.md +0 -0
  128. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/go-mod-smoke/go.mod +0 -0
  129. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/go-mod-smoke/go.sum +0 -0
  130. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-gradle/build.gradle +0 -0
  131. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-gradle/gradle.lockfile +0 -0
  132. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven/pom.xml +0 -0
  133. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-alias/pom.xml +0 -0
  134. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-chained/pom.xml +0 -0
  135. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-deep-chain/pom.xml +0 -0
  136. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-managed/pom.xml +0 -0
  137. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-parent/pom.xml +0 -0
  138. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-plugin-deps/pom.xml +0 -0
  139. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-profile-mgmt/pom.xml +0 -0
  140. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-props/pom.xml +0 -0
  141. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-revision/pom.xml +0 -0
  142. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/not-xml-pom.xml +0 -0
  143. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/package-lock.json +0 -0
  144. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/package.json +0 -0
  145. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/poetry.lock +0 -0
  146. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/pyproject.toml +0 -0
  147. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/truncated-poetry.lock +0 -0
  148. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/api/requirements.txt +0 -0
  149. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/web/package-lock.json +0 -0
  150. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/web/package.json +0 -0
  151. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/.corgea/deps.yml +0 -0
  152. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/package-lock.json +0 -0
  153. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/package.json +0 -0
  154. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/package-lock.json +0 -0
  155. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/package.json +0 -0
  156. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/packages/a/package.json +0 -0
  157. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/packages/b/package.json +0 -0
  158. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-pnpm/package.json +0 -0
  159. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-pnpm/pnpm-lock.yaml +0 -0
  160. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-stale/package-lock.json +0 -0
  161. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-stale/package.json +0 -0
  162. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-transitive/package-lock.json +0 -0
  163. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-transitive/package.json +0 -0
  164. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-yarn/package.json +0 -0
  165. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-yarn/yarn.lock +0 -0
  166. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-pip-nolock/requirements.txt +0 -0
  167. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry/poetry.lock +0 -0
  168. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry/pyproject.toml +0 -0
  169. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry-multi/poetry.lock +0 -0
  170. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry-multi/pyproject.toml +0 -0
  171. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/pyproject.toml +0 -0
  172. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/requirements.txt +0 -0
  173. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/uv.lock +0 -0
  174. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/README.md +0 -0
  175. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_clean.json +0 -0
  176. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_malware.json +0 -0
  177. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_unknown.json +0 -0
  178. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_vulnerable.json +0 -0
  179. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/package_profile.json +0 -0
  180. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/package_profile_not_found.json +0 -0
  181. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/harness_smoke.rs +0 -0
  182. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/list_code_quality.rs +0 -0
  183. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/list_resolution.rs +0 -0
  184. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/logging_e2e.rs +0 -0
  185. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/vuln_api_contract.rs +0 -0
  186. {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/wait_resolution.rs +0 -0
@@ -369,7 +369,7 @@ checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
369
369
 
370
370
  [[package]]
371
371
  name = "corgea"
372
- version = "1.14.0"
372
+ version = "1.14.2"
373
373
  dependencies = [
374
374
  "chrono",
375
375
  "clap",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "corgea"
3
- version = "1.14.0"
3
+ version = "1.14.2"
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.14.0
3
+ Version: 1.14.2
4
4
  Classifier: Development Status :: 5 - Production/Stable
5
5
  Classifier: Environment :: Console
6
6
  Classifier: Intended Audience :: Developers
@@ -48,20 +48,36 @@ evaluated; override with `CORGEA_BLOCKING_RULES_TIMEOUT_SECONDS`.
48
48
  trips: both are written before `--fail`/`--block-on` are evaluated, so a scan
49
49
  that exits 1 on a blocking rule still leaves its report behind to ingest.
50
50
 
51
- ### Gateway errors are retried, not surfaced
52
-
53
- Every Corgea API call the CLI makes — uploads included — replays itself when the
54
- platform's proxy answers `502 Bad Gateway`, waiting 10s, then 30s, then 50s. A
55
- request that is still answered 502 after those three retries fails the command
56
- in the usual way, so a pipeline exits non-zero on a real outage and rides out the
57
- blips a busy platform produces under parallel scans. Each retry is logged, and
58
- the count belongs to a single request: any successful call starts the next one
59
- with the full three retries again.
60
-
61
- The one place this stops early is `corgea upload`'s per-file source upload: a 502
62
- that outlives the retries there is taken as the platform being unavailable rather
63
- than one bad file, so the remaining paths are reported as unsent instead of each
64
- spending another 90 seconds.
51
+ ### Rate limits and gateway errors are retried
52
+
53
+ Neither `429 Too Many Requests` nor `502 Bad Gateway` is Corgea rejecting a
54
+ request on its merits, so both are retried rather than failed: the CLI waits
55
+ 10s, then 30s, then 50s, and a pipeline rides out the blips a busy platform
56
+ produces under parallel scans instead of failing on them. A request still
57
+ answered the same way after those three retries fails the command in the usual
58
+ way, so a real outage still exits non-zero. Each retry is logged, and the count
59
+ belongs to a single request: any other answer starts the next one with the full
60
+ three retries again. A `429` that names a `Retry-After` in seconds is honored,
61
+ up to two minutes for any one pause, and never shortens the pause below the
62
+ schedule.
63
+
64
+ Which requests get retried depends on which of the two it is:
65
+
66
+ - A `429` retries everything, `POST` and `PATCH` included. The rate limiter
67
+ declines the request before the API sees it, so nothing was created and
68
+ sending it again finishes the same work.
69
+ - A `502` retries reads only. It comes from the proxy rather than from Corgea,
70
+ so it is equally the answer for "the request never arrived" and for "the
71
+ request was processed and the reply was lost coming back" — and every write
72
+ the CLI sends creates something, so re-sending one does not finish the first
73
+ scan, it starts a second. A write's 502 goes straight to the caller.
74
+
75
+ Writes still retry network errors, where nothing reached Corgea at all.
76
+
77
+ `corgea upload` treats either status on a source upload — a 502, or a rate limit
78
+ that outlived its retries — as the platform being unavailable rather than one bad
79
+ file, and reports the remaining paths as unsent instead of collecting the same
80
+ answer once per path.
65
81
 
66
82
  ### Skipping a re-scan of the same commit
67
83
 
@@ -16,6 +16,7 @@ mod scanners {
16
16
  pub mod blast;
17
17
  pub mod fortify;
18
18
  pub mod parsers;
19
+ pub mod report_paths;
19
20
  }
20
21
  mod utils {
21
22
  pub mod api;
@@ -1,8 +1,10 @@
1
1
  use crate::cicd::*;
2
2
  use crate::log::debug;
3
3
  use crate::scanners::parsers::ScanParserFactory;
4
+ use crate::scanners::report_paths;
4
5
  use crate::{utils, Config};
5
6
  use reqwest::header;
7
+ use reqwest::Method;
6
8
  use serde_json::Value;
7
9
  use std::collections::HashSet;
8
10
  use std::io::{self, Read};
@@ -57,14 +59,46 @@ fn find_corgea_policy_files(root: &Path) -> Vec<String> {
57
59
  found
58
60
  }
59
61
 
60
- fn merge_corgea_policy_files(mut paths: Vec<String>, root: &Path) -> Vec<String> {
62
+ /// One file to send to `code-upload`.
63
+ struct SourceUpload {
64
+ /// Sent as `path=`, exactly as the report wrote it. The engine matches the
65
+ /// report's paths against this, so it stays as written even when the file
66
+ /// itself was found somewhere else.
67
+ report_path: String,
68
+ /// Where the file actually is on this machine.
69
+ local_path: PathBuf,
70
+ }
71
+
72
+ /// Pair every path a report names with the file to read for it, and append the
73
+ /// repo's `corgea.yaml` policy files.
74
+ ///
75
+ /// The prefix search runs on the report's paths alone, before the policy files
76
+ /// are added: those are found by walking `root`, so they always resolve, and
77
+ /// one of them counting as a path that "already resolves" would call off the
78
+ /// search for a report that matches nothing.
79
+ fn plan_source_uploads(root: &Path, report_paths: &[String]) -> Vec<SourceUpload> {
80
+ let prefix = report_paths::find_report_path_prefix(root, report_paths);
81
+
82
+ let mut uploads: Vec<SourceUpload> = report_paths
83
+ .iter()
84
+ .map(|path| SourceUpload {
85
+ report_path: path.clone(),
86
+ local_path: report_paths::local_report_path(root, path, &prefix),
87
+ })
88
+ .collect();
89
+
61
90
  for yaml in find_corgea_policy_files(root) {
62
- if !paths.iter().any(|path| path == &yaml) {
63
- debug(&format!("Including repo policy file: {yaml}"));
64
- paths.push(yaml);
91
+ if uploads.iter().any(|upload| upload.report_path == yaml) {
92
+ continue;
65
93
  }
94
+ debug(&format!("Including repo policy file: {yaml}"));
95
+ uploads.push(SourceUpload {
96
+ local_path: root.join(&yaml),
97
+ report_path: yaml,
98
+ });
66
99
  }
67
- paths
100
+
101
+ uploads
68
102
  }
69
103
 
70
104
  pub fn run_command(base_cmd: &String, mut command: Command) -> String {
@@ -280,7 +314,7 @@ pub fn upload_scan(
280
314
  project_name: Option<String>,
281
315
  ) -> Option<ScanUploadResult> {
282
316
  let cwd = std::env::current_dir().unwrap_or_else(|_| PathBuf::from("."));
283
- let paths = merge_corgea_policy_files(paths, &cwd);
317
+ let uploads = plan_source_uploads(&cwd, &paths);
284
318
  let in_ci = running_in_ci();
285
319
  let ci_platform = which_ci();
286
320
  let github_env_vars = get_github_env_vars();
@@ -328,13 +362,25 @@ pub fn upload_scan(
328
362
  let mut uploaded_paths = HashSet::new();
329
363
  let mut uploaded_count = 0;
330
364
  let mut upload_error_count = 0;
331
- let mut gateway_gave_up = false;
332
-
333
- 'files: for path in &paths {
334
- if !Path::new(&path).exists() {
365
+ let mut platform_declined = false;
366
+
367
+ 'files: for upload in &uploads {
368
+ let path = &upload.report_path;
369
+ let fp = upload.local_path.as_path();
370
+
371
+ if !fp.exists() {
372
+ // Name where the file was looked for whenever that is not the path
373
+ // in the report, so a prefix that resolved most of the report but
374
+ // not this path is visible rather than mystifying.
375
+ let looked_in = if fp.as_os_str() == path.as_str() {
376
+ String::new()
377
+ } else {
378
+ format!(" (looked for it at '{}')", fp.display())
379
+ };
335
380
  log::error!(
336
- "Required file {} not found which is required for the scan, exiting.",
337
- path
381
+ "Required file {}{} not found which is required for the scan, exiting.",
382
+ path,
383
+ looked_in
338
384
  );
339
385
  std::process::exit(1);
340
386
  }
@@ -348,14 +394,13 @@ pub fn upload_scan(
348
394
  base_url, api_base, run_id, path
349
395
  );
350
396
  debug(&format!("Uploading file: {}", path));
351
- let fp = Path::new(&path);
352
397
 
353
398
  let mut attempts = 0;
354
399
  let mut success = false;
355
400
 
356
401
  while attempts < 3 && !success {
357
402
  debug(&format!("POST: {}", src_upload_url));
358
- let res = utils::api::send_with_retries("a source file upload", || {
403
+ let res = utils::api::send_with_retries("a source file upload", &Method::POST, || {
359
404
  let form = reqwest::blocking::multipart::Form::new()
360
405
  .file("file", fp)
361
406
  .expect("Failed to read file");
@@ -373,18 +418,16 @@ pub fn upload_scan(
373
418
  "Code upload failed with status: {}. Response body: {}",
374
419
  status, body
375
420
  ));
376
- // A 502 that outlived the retry schedule is the platform
377
- // being unavailable, not this one file. Retrying it here
378
- // would spend the schedule twice over, and walking the
379
- // remaining paths would spend a fresh 90 seconds on each
380
- // of them, so stop uploading source files altogether.
381
- if utils::api::is_gateway_error(status) {
382
- log::warn!(
383
- "Failed to upload file {} after the gateway retries: {}",
384
- path,
385
- status
386
- );
387
- gateway_gave_up = true;
421
+ // A 502 or a rate limit that got this far is the
422
+ // platform being unavailable, not something wrong with
423
+ // this one file: the 502 because an upload is a write
424
+ // and so is never replayed, the 429 because its retries
425
+ // are already spent. Walking the remaining paths would
426
+ // just collect the same answer once per file, so stop
427
+ // uploading source files altogether.
428
+ if utils::api::is_transient_error(status) {
429
+ log::warn!("Failed to upload file {}: {}", path, status);
430
+ platform_declined = true;
388
431
  break 'files;
389
432
  }
390
433
  log::warn!("Failed to upload file {} {}... retrying", status, path);
@@ -419,12 +462,12 @@ pub fn upload_scan(
419
462
 
420
463
  // Everything the aborted walk never attempted still counts as unsent, or
421
464
  // the closing summary would report one failure for a whole skipped tree.
422
- if gateway_gave_up {
423
- let distinct: HashSet<&String> = paths.iter().collect();
465
+ if platform_declined {
466
+ let distinct: HashSet<&String> = uploads.iter().map(|upload| &upload.report_path).collect();
424
467
  let unsent = distinct.len() - uploaded_paths.len();
425
468
  upload_error_count += unsent;
426
469
  log::warn!(
427
- "Stopped uploading source files: Corgea was still answering 502 after the retries. {} of {} files were not sent.",
470
+ "Stopped uploading source files: Corgea is not accepting them right now. {} of {} files were not sent.",
428
471
  unsent,
429
472
  distinct.len()
430
473
  );
@@ -468,15 +511,16 @@ pub fn upload_scan(
468
511
  index + 1,
469
512
  total_chunks
470
513
  ));
471
- let response = utils::api::send_with_retries("a scan report chunk upload", || {
472
- client
473
- .post(&scan_upload_url)
474
- .header(header::CONTENT_TYPE, "application/json")
475
- .header("Upload-Offset", offset.to_string())
476
- .header("Upload-Length", input_size.to_string())
477
- .body(chunk.to_vec())
478
- .send()
479
- });
514
+ let response =
515
+ utils::api::send_with_retries("a scan report chunk upload", &Method::POST, || {
516
+ client
517
+ .post(&scan_upload_url)
518
+ .header(header::CONTENT_TYPE, "application/json")
519
+ .header("Upload-Offset", offset.to_string())
520
+ .header("Upload-Length", input_size.to_string())
521
+ .body(chunk.to_vec())
522
+ .send()
523
+ });
480
524
 
481
525
  let should_break = match &response {
482
526
  Ok(res) => {
@@ -519,7 +563,7 @@ pub fn upload_scan(
519
563
  last_response.expect("Failed to upload scan.")
520
564
  } else {
521
565
  debug(&format!("POST: {}", scan_upload_url));
522
- utils::api::send_with_retries("the scan report upload", || {
566
+ utils::api::send_with_retries("the scan report upload", &Method::POST, || {
523
567
  client
524
568
  .post(&scan_upload_url)
525
569
  .header(header::CONTENT_TYPE, "application/json")
@@ -600,7 +644,7 @@ pub fn upload_scan(
600
644
  if git_config_path.exists() {
601
645
  debug("Uploading .git/config");
602
646
  debug(&format!("POST: {}", git_config_upload_url));
603
- let res = utils::api::send_with_retries("the git config upload", || {
647
+ let res = utils::api::send_with_retries("the git config upload", &Method::POST, || {
604
648
  let form = reqwest::blocking::multipart::Form::new()
605
649
  .file("file", git_config_path)
606
650
  .expect("Failed to read file");
@@ -788,21 +832,109 @@ mod tests {
788
832
  );
789
833
  }
790
834
 
835
+ fn planned(root: &Path, report_paths: &[&str]) -> Vec<(String, String)> {
836
+ plan_source_uploads(
837
+ root,
838
+ &report_paths
839
+ .iter()
840
+ .map(|path| path.to_string())
841
+ .collect::<Vec<_>>(),
842
+ )
843
+ .into_iter()
844
+ .map(|upload| {
845
+ (
846
+ upload.report_path,
847
+ upload.local_path.to_string_lossy().into_owned(),
848
+ )
849
+ })
850
+ .collect()
851
+ }
852
+
853
+ #[test]
854
+ fn plan_source_uploads_appends_policy_files_and_skips_duplicates() {
855
+ let root = tempfile::tempdir().unwrap();
856
+ write_policy(&root.path().join("corgea.yaml"));
857
+ let policy = root
858
+ .path()
859
+ .join("corgea.yaml")
860
+ .to_string_lossy()
861
+ .into_owned();
862
+
863
+ assert_eq!(
864
+ planned(root.path(), &["src/source.py"]),
865
+ vec![
866
+ ("src/source.py".to_string(), "src/source.py".to_string()),
867
+ ("corgea.yaml".to_string(), policy.clone()),
868
+ ]
869
+ );
870
+ assert_eq!(
871
+ planned(root.path(), &["src/source.py", "corgea.yaml"]),
872
+ vec![
873
+ ("src/source.py".to_string(), "src/source.py".to_string()),
874
+ ("corgea.yaml".to_string(), "corgea.yaml".to_string()),
875
+ ]
876
+ );
877
+ }
878
+
879
+ /// Only the file read from disk is rebased. `path=` keeps the report's own
880
+ /// path, because that is what the engine matches the report against.
881
+ #[test]
882
+ fn plan_source_uploads_rebases_the_local_file_but_not_the_uploaded_path() {
883
+ let root = tempfile::tempdir().unwrap();
884
+ for path in ["src/a.py", "src/b.py"] {
885
+ std::fs::create_dir_all(root.path().join(path).parent().unwrap()).unwrap();
886
+ std::fs::write(root.path().join(path), "x = 1\n").unwrap();
887
+ }
888
+
889
+ assert_eq!(
890
+ planned(
891
+ root.path(),
892
+ &["/builds/acme/repo/src/a.py", "/builds/acme/repo/src/b.py"]
893
+ ),
894
+ vec![
895
+ (
896
+ "/builds/acme/repo/src/a.py".to_string(),
897
+ root.path().join("src/a.py").to_string_lossy().into_owned(),
898
+ ),
899
+ (
900
+ "/builds/acme/repo/src/b.py".to_string(),
901
+ root.path().join("src/b.py").to_string_lossy().into_owned(),
902
+ ),
903
+ ]
904
+ );
905
+ }
906
+
907
+ /// A policy file found by walking the root always exists, so letting one
908
+ /// into the prefix search would report the whole report as already
909
+ /// matching and call the search off.
791
910
  #[test]
792
- fn merge_corgea_policy_files_appends_missing_and_skips_duplicates() {
911
+ fn plan_source_uploads_settles_the_prefix_before_adding_policy_files() {
793
912
  let root = tempfile::tempdir().unwrap();
794
913
  write_policy(&root.path().join("corgea.yaml"));
914
+ for path in ["src/a.py", "src/b.py"] {
915
+ std::fs::create_dir_all(root.path().join(path).parent().unwrap()).unwrap();
916
+ std::fs::write(root.path().join(path), "x = 1\n").unwrap();
917
+ }
918
+
919
+ let plan = planned(root.path(), &["ci/src/a.py", "ci/src/b.py"]);
795
920
 
796
921
  assert_eq!(
797
- merge_corgea_policy_files(vec!["src/source.py".into()], root.path()),
798
- vec!["src/source.py".to_string(), "corgea.yaml".to_string()]
922
+ plan[0],
923
+ (
924
+ "ci/src/a.py".to_string(),
925
+ root.path().join("src/a.py").to_string_lossy().into_owned(),
926
+ )
799
927
  );
800
928
  assert_eq!(
801
- merge_corgea_policy_files(
802
- vec!["src/source.py".into(), "corgea.yaml".into()],
929
+ plan[2],
930
+ (
931
+ "corgea.yaml".to_string(),
803
932
  root.path()
933
+ .join("corgea.yaml")
934
+ .to_string_lossy()
935
+ .into_owned(),
804
936
  ),
805
- vec!["src/source.py".to_string(), "corgea.yaml".to_string()]
937
+ "the policy file is read from the root, never through the report's prefix"
806
938
  );
807
939
  }
808
940
  }
@@ -1,8 +1,9 @@
1
1
  use crate::scan::{upload_scan, ScanUploadResult};
2
2
  use crate::Config;
3
- use quick_xml::events::Event;
3
+ use quick_xml::events::{BytesStart, Event};
4
4
  use quick_xml::reader::Reader;
5
5
  use quick_xml::XmlVersion;
6
+ use std::collections::HashSet;
6
7
  use std::fs::File;
7
8
  use std::io;
8
9
  use std::io::{BufReader, Read};
@@ -68,6 +69,15 @@ pub fn parse(
68
69
  result
69
70
  }
70
71
 
72
+ /// The raw FVDL and the source files the engine will look up for it.
73
+ ///
74
+ /// A vulnerability can carry several `SourceLocation`s, where the earlier ones
75
+ /// are the enclosing scope and only the last is the finding itself, and the
76
+ /// engine reads that last one only. The earlier ones can sit in an unrelated
77
+ /// tree -- Fortify records its own bundled libraries under
78
+ /// `AppData/Local/Fortify/.../_fortify_libraries_/` -- which is not on the
79
+ /// machine running the CLI, so collecting them aborted the upload of a report
80
+ /// whose real files were all present.
71
81
  fn extract_file_path(scan_file: PathBuf) -> (String, Vec<String>) {
72
82
  let mut paths: Vec<String> = Vec::new();
73
83
 
@@ -84,6 +94,9 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec<String>) {
84
94
 
85
95
  let mut buf = Vec::new();
86
96
  let mut in_vulnerability = false;
97
+ let mut seen = HashSet::new();
98
+ // The last SourceLocation seen in the current Vulnerability.
99
+ let mut selected: Option<String> = None;
87
100
 
88
101
  loop {
89
102
  match xml_reader.read_event_into(&mut buf) {
@@ -94,48 +107,15 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec<String>) {
94
107
  if tag_name == b"Vulnerability" {
95
108
  in_vulnerability = true;
96
109
  } else if tag_name == b"SourceLocation" && in_vulnerability {
97
- for attr_result in e.attributes() {
98
- match attr_result {
99
- Ok(attr) => {
100
- let attr_key = attr.key.as_ref();
101
- if attr_key == b"path" {
102
- if let Ok(value) =
103
- attr.normalized_value(XmlVersion::Implicit1_0)
104
- {
105
- let path_str = value.to_string();
106
- if !paths.contains(&path_str) {
107
- paths.push(path_str);
108
- }
109
- }
110
- }
111
- }
112
- Err(e) => println!("Error processing attribute: {}", e),
113
- }
110
+ if let Some(path) = source_location_path(e) {
111
+ selected = Some(path);
114
112
  }
115
113
  }
116
114
  }
117
115
  Ok(Event::Empty(ref e)) => {
118
- let e_name = e.name();
119
- let tag_name = e_name.as_ref();
120
-
121
- if tag_name == b"SourceLocation" && in_vulnerability {
122
- for attr_result in e.attributes() {
123
- match attr_result {
124
- Ok(attr) => {
125
- let attr_key = attr.key.as_ref();
126
- if attr_key == b"path" {
127
- if let Ok(value) =
128
- attr.normalized_value(XmlVersion::Implicit1_0)
129
- {
130
- let path_str = value.to_string();
131
- if !paths.contains(&path_str) {
132
- paths.push(path_str);
133
- }
134
- }
135
- }
136
- }
137
- Err(e) => println!("Error processing attribute: {}", e),
138
- }
116
+ if e.name().as_ref() == b"SourceLocation" && in_vulnerability {
117
+ if let Some(path) = source_location_path(e) {
118
+ selected = Some(path);
139
119
  }
140
120
  }
141
121
  }
@@ -145,6 +125,12 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec<String>) {
145
125
 
146
126
  if tag_name == b"Vulnerability" {
147
127
  in_vulnerability = false;
128
+
129
+ if let Some(path) = selected.take() {
130
+ if seen.insert(path.clone()) {
131
+ paths.push(path);
132
+ }
133
+ }
148
134
  }
149
135
  }
150
136
  Ok(Event::Eof) => break,
@@ -157,6 +143,23 @@ fn extract_file_path(scan_file: PathBuf) -> (String, Vec<String>) {
157
143
  (contents, paths)
158
144
  }
159
145
 
146
+ /// The `path` attribute of a `SourceLocation`, with XML entities resolved.
147
+ fn source_location_path(element: &BytesStart) -> Option<String> {
148
+ for attr_result in element.attributes() {
149
+ match attr_result {
150
+ Ok(attr) if attr.key.as_ref() == b"path" => {
151
+ if let Ok(value) = attr.normalized_value(XmlVersion::Implicit1_0) {
152
+ return Some(value.to_string());
153
+ }
154
+ }
155
+ Ok(_) => {}
156
+ Err(e) => println!("Error processing attribute: {}", e),
157
+ }
158
+ }
159
+
160
+ None
161
+ }
162
+
160
163
  #[cfg(test)]
161
164
  mod tests {
162
165
  use super::*;
@@ -176,6 +179,8 @@ mod tests {
176
179
  <Vulnerabilities>
177
180
  <Vulnerability>
178
181
  <SourceLocation path="src/start/a&amp;b.java"></SourceLocation>
182
+ </Vulnerability>
183
+ <Vulnerability>
179
184
  <SourceLocation path="src/empty/App.java" line="42"/>
180
185
  </Vulnerability>
181
186
  <Vulnerability>
@@ -204,4 +209,29 @@ mod tests {
204
209
  ignores the out-of-scope SourceLocation, and de-duplicates"
205
210
  );
206
211
  }
212
+
213
+ /// The engine reads the last SourceLocation of a vulnerability and no
214
+ /// other, so the earlier ones must not be uploaded: they can name Fortify's
215
+ /// own bundled libraries, which are not on the machine running the CLI and
216
+ /// used to abort the upload before any file was sent.
217
+ #[test]
218
+ fn extract_file_path_keeps_only_the_location_the_finding_is_reported_at() {
219
+ let fvdl = r#"<?xml version="1.0" encoding="UTF-8"?>
220
+ <FVDL>
221
+ <Vulnerabilities>
222
+ <Vulnerability>
223
+ <SourceLocation path="C:/Users/dev/AppData/Local/Fortify/_fortify_libraries_/node/fs.d.ts" line="1"/>
224
+ <SourceLocation path="src/App.java" line="42"/>
225
+ </Vulnerability>
226
+ </Vulnerabilities>
227
+ </FVDL>"#;
228
+
229
+ let mut tmp = tempfile::NamedTempFile::new().expect("create temp fvdl");
230
+ tmp.write_all(fvdl.as_bytes()).expect("write fvdl");
231
+ tmp.flush().expect("flush fvdl");
232
+
233
+ let (_, paths) = extract_file_path(tmp.path().to_path_buf());
234
+
235
+ assert_eq!(paths, vec!["src/App.java".to_string()]);
236
+ }
207
237
  }