From 0066f171e0f4a81fcfcadc124f6ce3111220c6f0 Mon Sep 17 00:00:00 2001 From: Patrick Ryan Date: Sat, 10 Oct 2026 21:51:59 -0500 Subject: [PATCH 1/2] slim builder image: drop readelf/objdump/perl/pip, bump zlib r1, add VEX for remaining findings --- .github/scripts/check-vex-coverage.py | 62 +++ environments/alpine-3.24-musl/Containerfile | 20 +- .../alpine-3.24-musl/apk.lock.aarch64 | 4 +- environments/alpine-3.24-musl/apk.lock.x86_64 | 4 +- .../alpine-3.24-musl/vex/builder.openvex.json | 383 +++++++++++++++++- 5 files changed, 453 insertions(+), 20 deletions(-) create mode 100644 .github/scripts/check-vex-coverage.py diff --git a/.github/scripts/check-vex-coverage.py b/.github/scripts/check-vex-coverage.py new file mode 100644 index 0000000..4adcd5c --- /dev/null +++ b/.github/scripts/check-vex-coverage.py @@ -0,0 +1,62 @@ +#!/usr/bin/env python3 +# usage: check-vex-coverage.py SCAN.json SBOM.json VEX.json OUT.json +import json, sys, datetime as dt + +MAX_DB_AGE_DAYS, MIN_COMPONENTS = 3, 100 + +def find_key(o, key): + if isinstance(o, dict): + if key in o: return o[key] + o = list(o.values()) + if isinstance(o, list): + for v in o: + r = find_key(v, key) + if r is not None: return r + +def lists_pkg(stmt, name): + subs = [c.get("@id", "") for p in stmt.get("products", []) for c in p.get("subcomponents", [])] + return not subs or any(f"/{name}@" in s for s in subs) + +def ok_status(s, severity): + st = s.get("status") + if st == "fixed": return True + if st == "not_affected": return bool(s.get("justification") or s.get("impact_statement")) + # accepted risk: only below the HIGH gate, and only with a written action statement + if st == "affected": return severity in ("Medium", "Low", "Negligible") and bool(s.get("action_statement")) + return False + +def main(scan_p, sbom_p, vex_p, out_p): + scan, sbom, vex = (json.load(open(p)) for p in (scan_p, sbom_p, vex_p)) + problems, covered, uncovered, seen = [], [], [], set() + n = len(sbom.get("components", [])) + if n < MIN_COMPONENTS: + problems.append(f"control: SBOM has only {n} components") + desc = scan.get("descriptor", {}) + built = find_key(desc.get("db", {}), "built") + try: + age = (dt.datetime.now(dt.timezone.utc) - dt.datetime.fromisoformat(str(built).replace("Z", "+00:00"))).days + if age > MAX_DB_AGE_DAYS: problems.append(f"vulnerability DB is {age} days old") + except Exception: + problems.append("cannot read the vulnerability DB build date from the scan") + by_id = {} + for s in vex.get("statements", []): + v = s.get("vulnerability", {}) + for i in {v.get("name"), *v.get("aliases", [])} - {None}: + by_id.setdefault(i, []).append(s) + for m in scan.get("matches", []): + ids = {m["vulnerability"]["id"], *(r["id"] for r in m.get("relatedVulnerabilities", []))} + seen |= ids + pkg = m["artifact"] + hit = [s for i in ids for s in by_id.get(i, []) if ok_status(s, m["vulnerability"].get("severity")) and lists_pkg(s, pkg["name"])] + row = {"ids": sorted(ids), "package": pkg["name"], "version": pkg.get("version")} + (covered if hit else uncovered).append({**row, "status": hit[0]["status"]} if hit else row) + unused = sorted(i for i in by_id if i not in seen) + verdict = "pass" if not problems and not uncovered else "fail" + json.dump({"verdict": verdict, "scanner": desc.get("name"), "scanner_version": desc.get("version"), + "db_built": built, "sbom_components": n, "findings": len(covered) + len(uncovered), + "covered": covered, "uncovered": uncovered, "problems": problems, + "vex_statements_without_finding": unused}, open(out_p, "w"), indent=2, sort_keys=True) + print(f"{verdict}: {len(covered)} covered, {len(uncovered)} uncovered, problems={problems}") + return 0 if verdict == "pass" else 1 + +sys.exit(main(*sys.argv[1:5])) diff --git a/environments/alpine-3.24-musl/Containerfile b/environments/alpine-3.24-musl/Containerfile index 1160a98..553a9bb 100644 --- a/environments/alpine-3.24-musl/Containerfile +++ b/environments/alpine-3.24-musl/Containerfile @@ -20,14 +20,21 @@ # Get the digest with: skopeo inspect --format '{{.Digest}}' docker://docker.io/library/alpine:3.24 FROM docker.io/library/alpine:3.24@sha256:294b683cb724975bec92580e1e685676bd4b50bda910ddb8c51d4cabeaec77e6 -# build-base: gcc/make/etc - the C toolchain Rust's linker needs. +# Toolchain spelled out instead of build-base, which also pulls in `patch` (never used). +# perl is deliberately absent: the dbt-oss build does not need it, and git-perl would come with it. +# readelf/objdump are deleted: the build never calls them. # python3-dev: maturin's build backend links against libpython. # openssl-dev/libffi-dev: common transitive needs for Rust crates with # native deps. # NOTE: Alpine has no package literally named "protoc" - the binary ships # in "protobuf". Do not reintroduce "protoc" here. RUN apk add --no-cache \ - build-base \ + binutils \ + file \ + fortify-headers \ + g++ \ + gcc \ + libc-dev \ curl \ git \ python3 \ @@ -37,11 +44,12 @@ RUN apk add --no-cache \ openssl-libs-static \ libffi-dev \ pkgconfig \ - perl \ make \ patchelf \ protobuf \ - protobuf-dev + protobuf-dev \ + && find / -xdev ! -type d \( -name readelf -o -name objdump \) -delete \ + && ! find / -xdev ! -type d \( -name '*readelf*' -o -name '*objdump*' \) | grep . # Fail the image build in seconds if these didn't land. RUN patchelf --version && protoc --version @@ -104,7 +112,9 @@ COPY requirements-builder.txt /tmp/requirements-builder.txt RUN pip install --break-system-packages --no-cache-dir --require-hashes \ -r /tmp/requirements-builder.txt \ && rm /tmp/requirements-builder.txt \ - && maturin --version + && maturin --version \ + && apk del py3-pip \ + && ! python3 -m pip --version >/dev/null 2>&1 LABEL org.opencontainers.image.source="https://github.com/patrickryankenneth/python-wheels-builder" WORKDIR /build diff --git a/environments/alpine-3.24-musl/apk.lock.aarch64 b/environments/alpine-3.24-musl/apk.lock.aarch64 index 4b9acf2..b907b03 100644 --- a/environments/alpine-3.24-musl/apk.lock.aarch64 +++ b/environments/alpine-3.24-musl/apk.lock.aarch64 @@ -185,6 +185,6 @@ scanelf-1.3.9-r1 sqlite-libs-3.53.4-r0 ssl_client-1.37.0-r31 xz-libs-5.8.4-r0 -zlib-1.3.2-r0 -zlib-dev-1.3.2-r0 +zlib-1.3.2-r1 +zlib-dev-1.3.2-r1 zstd-libs-1.5.7-r2 diff --git a/environments/alpine-3.24-musl/apk.lock.x86_64 b/environments/alpine-3.24-musl/apk.lock.x86_64 index 4b9acf2..b907b03 100644 --- a/environments/alpine-3.24-musl/apk.lock.x86_64 +++ b/environments/alpine-3.24-musl/apk.lock.x86_64 @@ -185,6 +185,6 @@ scanelf-1.3.9-r1 sqlite-libs-3.53.4-r0 ssl_client-1.37.0-r31 xz-libs-5.8.4-r0 -zlib-1.3.2-r0 -zlib-dev-1.3.2-r0 +zlib-1.3.2-r1 +zlib-dev-1.3.2-r1 zstd-libs-1.5.7-r2 diff --git a/environments/alpine-3.24-musl/vex/builder.openvex.json b/environments/alpine-3.24-musl/vex/builder.openvex.json index 24af275..9da42de 100644 --- a/environments/alpine-3.24-musl/vex/builder.openvex.json +++ b/environments/alpine-3.24-musl/vex/builder.openvex.json @@ -6,38 +6,399 @@ "version": 1, "statements": [ { - "vulnerability": {"name": "CVE-2026-33055"}, - "products": [{"@id": "pkg:cargo/tar@0.4.40"}], + "vulnerability": { + "name": "CVE-2026-33055" + }, + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40" + } + ], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "tar 0.4.40 is linked into sccache 0.18.0 only for creating archives (tar::Builder). The vulnerable behavior is in reading archives. tar::Archive is used only under src/bin/sccache-dist/, which requires the dist-server feature. The builder installs with --no-default-features and the image has no sccache-dist binary (asserted by assert-image.sh). Review by 2027-01-03, or on any sccache or tar version change." }, { - "vulnerability": {"name": "CVE-2026-33056"}, - "products": [{"@id": "pkg:cargo/tar@0.4.40"}], + "vulnerability": { + "name": "CVE-2026-33056" + }, + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40" + } + ], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "The flaw is in unpacking crafted archives. sccache calls no unpack path outside sccache-dist, which is not built or shipped (see CVE-2026-33055). Review by 2027-01-03, or on any sccache or tar version change." }, { - "vulnerability": {"name": "GHSA-3pv8-6f4r-ffg2"}, - "products": [{"@id": "pkg:cargo/tar@0.4.40"}], + "vulnerability": { + "name": "GHSA-3pv8-6f4r-ffg2" + }, + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40" + } + ], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "The flaw is PAX header desynchronization when reading a tar stream. sccache reads tar streams only in sccache-dist, which is not built or shipped (see CVE-2026-33055). Review by 2027-01-03, or on any sccache or tar version change." }, { - "vulnerability": {"name": "GHSA-cq8v-f236-94qc"}, - "products": [{"@id": "pkg:cargo/rand@0.8.5"}], + "vulnerability": { + "name": "GHSA-cq8v-f236-94qc" + }, + "products": [ + { + "@id": "pkg:cargo/rand@0.8.5" + } + ], "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path", "impact_statement": "The advisory requires a custom logger that calls into rand. sccache 0.18.0 source has no set_logger, set_boxed_logger or impl Log, and its only rand usage is OsRng and RngCore in four files. Review by 2027-01-03, or on any sccache or rand version change." }, { - "vulnerability": {"name": "CVE-2026-25541", "aliases": ["GHSA-434x-w66g-qw3r"]}, - "products": [{"@id": "pkg:cargo/bytes@1.10.1"}], + "vulnerability": { + "name": "CVE-2026-25541", + "aliases": [ + "GHSA-434x-w66g-qw3r" + ] + }, + "products": [ + { + "@id": "pkg:cargo/bytes@1.10.1" + } + ], "status": "affected", "action_statement": "Accepted, not suppressed. bytes 1.10.1 is in sccache's server protocol path (BytesMut in src/server.rs). Supporting controls, not a proof of non-exploitability: the server binds 127.0.0.1 or a Unix socket, and the frame length is capped at 8 MiB. Upstream main still resolves bytes 1.10.1 and has no release after 0.18.0. Severity MEDIUM, below the HIGH gate. Review by 2027-01-03, or when sccache updates bytes." + }, + { + "vulnerability": { + "name": "CVE-2025-69644" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69647" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69648" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69649" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69650" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69651" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-69652" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2026-6844" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2026-6845" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_present", + "impact_statement": "Affects readelf/objdump only. Both binaries are deleted in the Containerfile and the build asserts none remain." + }, + { + "vulnerability": { + "name": "CVE-2025-60876" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/busybox@1.37.0-r31?arch=x86_64&distro=alpine-3.24.2" + }, + { + "@id": "pkg:apk/alpine/busybox-binsh@1.37.0-r31?arch=x86_64&distro=alpine-3.24.2&upstream=busybox" + }, + { + "@id": "pkg:apk/alpine/ssl_client@1.37.0-r31?arch=x86_64&distro=alpine-3.24.2&upstream=busybox" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "wget applet only. The build uses no network tools (sccache built without network features) and never invokes wget; ssl_client is only used by wget." + }, + { + "vulnerability": { + "name": "CVE-2025-15367" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/python3@3.14.8-r0?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "poplib is not imported by anything in the builder." + }, + { + "vulnerability": { + "name": "CVE-2026-87910" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/python3@3.14.8-r0?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "Affects only the tarfile fallback for filesystems without link support; the builder runs on Linux, which supports links." + }, + { + "vulnerability": { + "name": "CVE-2026-12345" + }, + "timestamp": "2026-10-11T02:24:38.177437+00:00", + "status": "affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/python3@3.14.8-r0?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "action_statement": "Accepted: exploiting needs a local attacker modifying a temp tree during cleanup; the builder is an ephemeral single-purpose container. Revisit when Alpine ships a fix." + }, + { + "vulnerability": { + "name": "CVE-2026-3441" + }, + "timestamp": "2026-10-11T02:39:14.930625+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "XCOFF-only code. No XCOFF/AIX target vector is registered in this binutils build (ld, nm, objcopy and strip list only ELF, PE, BPF and raw formats), so no input is ever parsed as XCOFF." + }, + { + "vulnerability": { + "name": "CVE-2026-3442" + }, + "timestamp": "2026-10-11T02:39:14.930625+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "XCOFF-only code. No XCOFF/AIX target vector is registered in this binutils build (ld, nm, objcopy and strip list only ELF, PE, BPF and raw formats), so no input is ever parsed as XCOFF." + }, + { + "vulnerability": { + "name": "CVE-2026-4647" + }, + "timestamp": "2026-10-11T02:39:14.930625+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "XCOFF-only code. No XCOFF/AIX target vector is registered in this binutils build (ld, nm, objcopy and strip list only ELF, PE, BPF and raw formats), so no input is ever parsed as XCOFF." + }, + { + "vulnerability": { + "name": "CVE-2026-6846" + }, + "timestamp": "2026-10-11T02:39:14.930625+00:00", + "status": "not_affected", + "products": [ + { + "@id": "pkg:cargo/tar@0.4.40", + "subcomponents": [ + { + "@id": "pkg:apk/alpine/binutils@2.45.1-r1?arch=x86_64&distro=alpine-3.24.2" + } + ] + } + ], + "justification": "vulnerable_code_not_in_execute_path", + "impact_statement": "XCOFF-only code. No XCOFF/AIX target vector is registered in this binutils build (ld, nm, objcopy and strip list only ELF, PE, BPF and raw formats), so no input is ever parsed as XCOFF." } ] -} +} \ No newline at end of file From 9bea4b25239bb48f237c1263457b4cf617caaa15 Mon Sep 17 00:00:00 2001 From: Patrick Ryan Date: Sat, 10 Oct 2026 21:57:54 -0500 Subject: [PATCH 2/2] release: scan image with pinned grype and check VEX coverage per arch --- .github/workflows/release.yml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 053fa5d..822a966 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,6 +30,7 @@ permissions: {} env: IMAGE: ghcr.io/patrickryankenneth/python-wheels-builder-alpine SYFT_VERSION: "1.54.0" + GRYPE_VERSION: "0.120.0" VEX_PATH: environments/alpine-3.24-musl/vex/builder.openvex.json VEX_PREDICATE_TYPE: https://openvex.dev/ns/v0.2.0 ASSERT_PREDICATE_TYPE: https://github.com/patrickryankenneth/python-wheels-builder/predicate/assert-image/v1 @@ -76,10 +77,12 @@ jobs: runner: ubuntu-24.04 syft_arch: amd64 syft_sha256: 54a87372498168b2d033e876fd41fa4e8035b872699e525a57046e1f2f09c860 + grype_sha256: a5a1218dce63acdac152a6b3b5bb366e7267e36f4069848cf455543b3fa5700e - arch: aarch64 runner: ubuntu-24.04-arm syft_arch: arm64 syft_sha256: ee6d4566373a05b344bc6b5f1706f14419bf9338ba39ff686e247deefe9b8818 + grype_sha256: bc0e52b1a0de37e2ff021c4924d689dce7dcff2e7d74b39aea16c0453e69be18 runs-on: ${{ matrix.runner }} permissions: contents: read @@ -172,6 +175,37 @@ jobs: PY cat "out/assert-image.$ARCH.json" + - name: Install pinned Grype + env: + GRYPE_ARCH: ${{ matrix.syft_arch }} + GRYPE_SHA256: ${{ matrix.grype_sha256 }} + run: | + set -euo pipefail + f="grype_${GRYPE_VERSION}_linux_${GRYPE_ARCH}.tar.gz" + curl -fsSL --proto '=https' -o "$f" \ + "https://github.com/anchore/grype/releases/download/v${GRYPE_VERSION}/$f" + echo "$GRYPE_SHA256 $f" | sha256sum --check --strict - + mkdir -p "$RUNNER_TEMP/grype" + tar -xzf "$f" -C "$RUNNER_TEMP/grype" grype + + - name: Scan the image and check VEX coverage + env: + GRYPE_CHECK_FOR_APP_UPDATE: "false" + run: | + set -euo pipefail + "$RUNNER_TEMP/grype/grype" db update + "$RUNNER_TEMP/grype/grype" "registry:$IMAGE@$D" -o json > "out/vuln-scan.$ARCH.json" + python3 .github/scripts/check-vex-coverage.py \ + "out/vuln-scan.$ARCH.json" "out/sbom.$ARCH.cdx.json" \ + "$VEX_PATH" "out/vex-coverage.$ARCH.json" | tee -a "$GITHUB_STEP_SUMMARY" + + - name: Upload VEX coverage report + if: always() + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 + with: + name: vex-coverage-${{ matrix.arch }} + path: out/vex-coverage.${{ matrix.arch }}.json + - name: Attest CycloneDX SBOM uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2 with: