From b193d4555f7b2e512d13d630c296e0f8816de138 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Tue, 21 Jul 2026 12:51:19 +0200 Subject: [PATCH] fix(audit): accept empty OSV source reports --- tests/test_security_audit_wrapper.py | 100 ++++++++++++++++++++++++++- tools/checks/check-security-audit.sh | 3 +- 2 files changed, 100 insertions(+), 3 deletions(-) diff --git a/tests/test_security_audit_wrapper.py b/tests/test_security_audit_wrapper.py index ee81fd9..f4a5edb 100644 --- a/tests/test_security_audit_wrapper.py +++ b/tests/test_security_audit_wrapper.py @@ -133,8 +133,82 @@ class SecurityAuditWrapperTests(unittest.TestCase): ) path.chmod(0o755) + def _install_full_mode_stubs(self) -> None: + self._write_stub( + "trivy", + r""" + if [[ " $* " == *" --version "* ]]; then + echo 'trivy stub 1.0' + exit 0 + fi + output='' + while [[ $# -gt 0 ]]; do + if [[ "$1" == '--output' ]]; then + output="$2" + shift 2 + else + shift + fi + done + printf '%s\n' '{"version":"2.1.0","runs":[]}' > "$output" + """, + ) + self._write_stub( + "osv-scanner", + r""" + if [[ " $* " == *" --version "* ]]; then + echo 'osv-scanner stub 1.0' + exit 0 + fi + if [[ " $* " == *" scan --help "* ]]; then + exit 0 + fi + output='' + while [[ $# -gt 0 ]]; do + if [[ "$1" == '--output-file' ]]; then + output="$2" + shift 2 + else + shift + fi + done + printf '{"results": %s}\n' "${STUB_OSV_RESULTS:-null}" > "$output" + """, + ) + self._write_stub( + "jscpd", + r""" + if [[ " $* " == *" --version "* ]]; then + echo 'jscpd stub 1.0' + exit 0 + fi + output='' + while [[ $# -gt 0 ]]; do + if [[ "$1" == '--output' ]]; then + output="$2" + shift 2 + else + shift + fi + done + mkdir -p "$output" + printf '%s\n' '{"duplicates":[],"statistics":{}}' > "$output/jscpd-report.json" + """, + ) + for tool in ("pip-audit", "npm", "radon", "xenon"): + self._write_stub( + tool, + f""" + if [[ " $* " == *" --version "* ]]; then + echo '{tool} stub 1.0' + exit 0 + fi + exit 0 + """, + ) + def _run( - self, *arguments: str, **environment: str + self, *arguments: str, mode: str = "quick", **environment: str ) -> tuple[subprocess.CompletedProcess[str], Path]: reports = self.temporary_root / "reports" env = os.environ.copy() @@ -151,7 +225,7 @@ class SecurityAuditWrapperTests(unittest.TestCase): "bash", str(self.checkout / "tools" / "checks" / AUDIT_SCRIPT.name), "--mode", - "quick", + mode, "--scope", "current", "--reports-dir", @@ -215,6 +289,28 @@ class SecurityAuditWrapperTests(unittest.TestCase): (reports / "step-status.tsv").read_text(), ) + def test_osv_null_results_are_valid_but_wrong_shapes_fail(self) -> None: + self._install_full_mode_stubs() + + result, reports = self._run("--report-only", mode="full") + self.assertEqual(0, result.returncode, result.stderr) + osv_report = json.loads( + (reports / "osv-scanner-checkout.json").read_text(encoding="utf-8") + ) + self.assertIsNone(osv_report["results"]) + + invalid_result, invalid_reports = self._run( + "--report-only", + mode="full", + STUB_OSV_RESULTS="{}", + ) + self.assertEqual(1, invalid_result.returncode) + self.assertIn( + "invalid OSV-Scanner report", + invalid_result.stderr, + ) + self.assertEqual(1, self._manifest(invalid_reports)["execution_error_status"]) + def test_workspace_mutation_invalidates_the_audit(self) -> None: result, reports = self._run( "--report-only", diff --git a/tools/checks/check-security-audit.sh b/tools/checks/check-security-audit.sh index df35171..b063922 100644 --- a/tools/checks/check-security-audit.sh +++ b/tools/checks/check-security-audit.sh @@ -444,7 +444,8 @@ for raw_path in sorted(set(sys.argv[2:])): errors.append(f"{relative_path}: npm audit returned an invalid or error report") elif report_name.startswith("osv-scanner-") and ( not isinstance(payload, dict) - or not isinstance(payload.get("results"), list) + or "results" not in payload + or (payload["results"] is not None and not isinstance(payload["results"], list)) ): errors.append(f"{relative_path}: invalid OSV-Scanner report") elif report_name == "jscpd-report.json" and (