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.
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/Cargo.lock +1 -1
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/Cargo.toml +1 -1
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/PKG-INFO +1 -1
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/README.md +30 -14
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/main.rs +1 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scan.rs +178 -46
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/fortify.rs +69 -39
- corgea_cli-1.14.2/src/scanners/report_paths.rs +632 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/api.rs +452 -173
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/main.rs +2 -1
- 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
- corgea_cli-1.14.2/tests/cloud_commands_e2e/upload_report_paths.rs +168 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.codex/hooks.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/npm-publish.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/release-binaries.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/release.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/staging-contract.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.github/workflows/test.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/.gitignore +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/AGENTS.md +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/LICENSE +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/RELEASING.md +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/bin/corgea.js +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/build_release.sh +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/examples/deps_skill.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/harness +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/pyproject.toml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/scripts/check-linux-binary.sh +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/scripts/npm/bundle-binaries.js +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/skills/corgea/SKILL.md +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/advisories.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/authorize.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/cicd.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/config.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/catalog.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/detect.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/diff.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/evaluate.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/maven.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/npm.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/ecosystems/pypi.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/explain.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/findings.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/model.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/npm_lock.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/parse/python_lock.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/policy.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/report.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/run.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/skill.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/common.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/correctness_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/detect_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/diff_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/explain_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/findings_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/maven_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/npm_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/policy_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/pypi_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/report_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/robustness_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/deps/tests/slice0_tests.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/images.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/incremental.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/inspect.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/lib.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/list.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/log.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/mcp.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/detect.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/exec.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/parse.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/render.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/test_support.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/tree.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/uv.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/precheck/verdict.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/blast.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/checkmarx.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/coverity.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/sarif.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/scanners/parsers/semgrep.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/setup_hooks.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/skill.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/skip_scan.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/targets.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/generic.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/utils/terminal.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/verify_deps/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/verify_deps/registry.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/vuln_api/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/vuln_api_stub/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/src/wait.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_advisories.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_bare_install.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_config_errors.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_deps.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_deps_skill.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_exec_fallback.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_install.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_mcp.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_npm_ci.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_provenance.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_remediation.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_include_image.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_sbom.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_scan_wait_terminal_state.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_tree.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_uv_sync.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cli_verdict.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/block_on_report.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/common/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/inspect.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_incremental.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_list.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/scan_skip.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/cloud_commands_e2e/upload_wait.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/common/mod.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/README.md +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/go-mod-smoke/go.mod +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/go-mod-smoke/go.sum +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-gradle/build.gradle +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-gradle/gradle.lockfile +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-alias/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-chained/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-deep-chain/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-managed/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-parent/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-plugin-deps/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-profile-mgmt/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-props/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/java-maven-revision/pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/not-xml-pom.xml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/poetry.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/pyproject.toml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/malformed/truncated-poetry.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/api/requirements.txt +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/web/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/mixed-monorepo/services/web/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/.corgea/deps.yml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-app/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/packages/a/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-monorepo/packages/b/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-pnpm/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-pnpm/pnpm-lock.yaml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-stale/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-stale/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-transitive/package-lock.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-transitive/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-yarn/package.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/node-yarn/yarn.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-pip-nolock/requirements.txt +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry/poetry.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry/pyproject.toml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry-multi/poetry.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-poetry-multi/pyproject.toml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/pyproject.toml +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/requirements.txt +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/python-uv-requirements/uv.lock +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/README.md +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_clean.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_malware.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_unknown.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/check_vulnerable.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/package_profile.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/fixtures/vuln_api/package_profile_not_found.json +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/harness_smoke.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/list_code_quality.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/list_resolution.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/logging_e2e.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/vuln_api_contract.rs +0 -0
- {corgea_cli-1.14.0 → corgea_cli-1.14.2}/tests/wait_resolution.rs +0 -0
|
@@ -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
|
-
###
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
|
|
@@ -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
|
-
|
|
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
|
|
63
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
332
|
-
|
|
333
|
-
'files: for
|
|
334
|
-
|
|
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
|
|
377
|
-
// being unavailable, not
|
|
378
|
-
//
|
|
379
|
-
//
|
|
380
|
-
//
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
|
423
|
-
let distinct: HashSet<&String> =
|
|
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
|
|
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 =
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
|
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
|
-
|
|
798
|
-
|
|
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
|
-
|
|
802
|
-
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
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
|
-
|
|
119
|
-
|
|
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&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
|
}
|