diff --git a/scripts/release_evidence.py b/scripts/release_evidence.py index 3554a7bb..97ce654a 100644 --- a/scripts/release_evidence.py +++ b/scripts/release_evidence.py @@ -718,6 +718,15 @@ 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): + 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") checksum = database.get("checksum") diff --git a/tests/test_release_evidence.py b/tests/test_release_evidence.py index c8d5b5e2..f59a283c 100644 --- a/tests/test_release_evidence.py +++ b/tests/test_release_evidence.py @@ -1113,6 +1113,60 @@ 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_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 = [ {