From de500506f592c2f9a74ba7cf2986a875c98b217b Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Tue, 25 Aug 2026 11:22:46 -0400 Subject: [PATCH 1/2] fix(release): parse Grype 0.110 nested db.status identity Grype >= 0.110 nests the vulnerability database identity under descriptor.db.status (built / schemaVersion / checksum inside the from URL); release_evidence.py still read the pre-0.110 flat db layout and failed the v1.6 release run with "container vulnerability database identity is incomplete" after every other gate had passed. Accept both shapes: when db.status is a mapping, merge its fields over the flat ones before validating. Adds a regression test using the exact structure emitted by scan-action/Grype 0.110.0 in run 32860273692. Co-authored-by: CommandCodeBot --- scripts/release_evidence.py | 5 +++++ tests/test_release_evidence.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/scripts/release_evidence.py b/scripts/release_evidence.py index 3554a7bb..f54664e8 100644 --- a/scripts/release_evidence.py +++ b/scripts/release_evidence.py @@ -718,6 +718,11 @@ def container_scan_artifact(root: Path, path: Path) -> dict[str, Any]: database = descriptor.get("db") if not isinstance(database, dict): raise EvidenceError("container vulnerability report must identify its database") + # Grype >= 0.110 nests the database identity under db.status; older releases + # wrote built/schemaVersion/checksum|from directly on db. Accept both shapes. + status = database.get("status") + if isinstance(status, dict): + database = {**database, **status} built = database.get("built") schema_version = database.get("schemaVersion") checksum = database.get("checksum") diff --git a/tests/test_release_evidence.py b/tests/test_release_evidence.py index c8d5b5e2..5cc74686 100644 --- a/tests/test_release_evidence.py +++ b/tests/test_release_evidence.py @@ -1113,6 +1113,36 @@ def test_release_evidence_accepts_identified_current_grype_database_shape(tmp_pa } +def test_release_evidence_accepts_grype_0110_nested_db_status_shape(tmp_path): + root = _root(tmp_path) + dist = _dist(root) + inputs = _release_inputs(root, dist) + report = json.loads(inputs["image_scan"].read_text(encoding="utf-8")) + report["descriptor"]["db"] = { + "status": { + "schemaVersion": "v6.1.9", + "from": ( + "https://grype.anchore.io/databases/v6/vulnerability-db_v6.1.9_" + "2026-08-25T00:17:00Z_1787638635.tar.zst" + "?checksum=sha256%3A" + "f" * 64 + ), + "built": "2026-08-25T06:17:15Z", + "path": "/home/runner/.cache/grype/db/6/vulnerability.db", + "valid": True, + }, + "providers": {"ubuntu": {"captured": "2026-08-25T00:19:07Z"}}, + } + inputs["image_scan"].write_text(json.dumps(report), encoding="utf-8") + + evidence = _build(root, dist, inputs=inputs) + + assert evidence["container"]["vulnerability_scan"]["database"] == { + "built": "2026-08-25T06:17:15Z", + "schema_version": "v6.1.9", + "checksum": "sha256:" + "f" * 64, + } + + def test_repair_run_candidates_are_newest_first_and_bound_to_tag_commit_event(): runs = [ { From ba8c991f21e24134f4e9f8f3e23d31fec418f0ca Mon Sep 17 00:00:00 2001 From: Coding-Dev-Tools Date: Tue, 25 Aug 2026 12:12:24 -0400 Subject: [PATCH 2/2] Reject nested Grype database statuses marked invalid (db.status.valid: false) Codex review P1 on #170: the db.status merge ignored the scanner-declared validity flag, so an invalid-database report with zero matches could pass the severity gate and ship as release security evidence. Reject such reports explicitly; adds a regression test. Co-authored-by: CommandCodeBot --- scripts/release_evidence.py | 4 ++++ tests/test_release_evidence.py | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/scripts/release_evidence.py b/scripts/release_evidence.py index f54664e8..97ce654a 100644 --- a/scripts/release_evidence.py +++ b/scripts/release_evidence.py @@ -722,6 +722,10 @@ def container_scan_artifact(root: Path, path: Path) -> dict[str, Any]: # wrote built/schemaVersion/checksum|from directly on db. Accept both shapes. status = database.get("status") if isinstance(status, dict): + if status.get("valid") is False: + raise EvidenceError( + "container vulnerability database is marked invalid by the scanner" + ) database = {**database, **status} built = database.get("built") schema_version = database.get("schemaVersion") diff --git a/tests/test_release_evidence.py b/tests/test_release_evidence.py index 5cc74686..f59a283c 100644 --- a/tests/test_release_evidence.py +++ b/tests/test_release_evidence.py @@ -1143,6 +1143,30 @@ def test_release_evidence_accepts_grype_0110_nested_db_status_shape(tmp_path): } +def test_release_evidence_rejects_grype_0110_database_marked_invalid(tmp_path): + root = _root(tmp_path) + dist = _dist(root) + inputs = _release_inputs(root, dist) + report = json.loads(inputs["image_scan"].read_text(encoding="utf-8")) + report["descriptor"]["db"] = { + "status": { + "schemaVersion": "v6.1.9", + "from": ( + "https://grype.anchore.io/databases/v6/vulnerability-db_v6.1.9_" + "2026-08-25T00:17:00Z_1787638635.tar.zst" + "?checksum=sha256%3A" + "f" * 64 + ), + "built": "2026-08-25T06:17:15Z", + "path": "/home/runner/.cache/grype/db/6/vulnerability.db", + "valid": False, + }, + } + inputs["image_scan"].write_text(json.dumps(report), encoding="utf-8") + + with pytest.raises(EvidenceError, match="marked invalid"): + _build(root, dist, inputs=inputs) + + def test_repair_run_candidates_are_newest_first_and_bound_to_tag_commit_event(): runs = [ {