ci: attest an OpenVEX document per variant at tag time
A scan of the published images reports no operating system package: every variant ships FROM scratch and carries no rpm database. What it reports is the Go binary, and a few of its module findings have no fix available at the pinned upstream tag. Each published digest now carries our assessment of those as a cosign-signed OpenVEX 0.2.0 document, so a consumer does not re-derive it. security/vex/statements.json is the curated source; scripts/vex-render.sh renders one variant against one digest, and scripts/vex-lint.sh asserts it and all six documents on every pipeline.
What was measured. Trivy 0.74.0 against the published scratch tags returns one target per image, the Go binary. All six were scanned and all six report the same six findings: standard, fips and warmer at 8d7eb50, and standard, debug, fips-debug and fips-warmer at 8c36910. The debug variants' static busybox is not a scanner target and contributes no finding. One UBI9 builder stage and one module graph produce every binary, so one triage covers all six and the documents differ only in the digest they name.
Statements. Six, identical across the variants. Five are affected, each naming what holds the module below the fix. CVE-2026-17106 on moby/go-archive is held at v0.1.0 by a replace directive in upstream's own go.mod; the build applies go-overrides.txt with go get, which writes a require, and a replace beats a require. Four docker/docker findings, CVE-2026-41567, CVE-2026-42306, CVE-2026-41568 and CVE-2026-33997, are fixed only in Docker Engine 29, at the module path github.com/moby/moby/v2; the path this build imports ends at v28.5.2+incompatible, so no override reaches the fix. One is not_affected, justified vulnerable_code_not_present: GO-2026-5932 covers golang.org/x/crypto/openpgp, and the binary links no symbol under it, carrying ProtonMail/go-crypto/openpgp v1.3.0 in 607 symbols instead.
Fixed rather than annotated. Three findings were candidates for a statement and got a fix instead: CVE-2026-84304 on google.golang.org/grpc and CVE-2026-61711 and CVE-2026-61712 on moby/buildkit. Raising a module is a build-input change, so the override MR raised grpc to v1.83.1 and buildkit to v0.31.1 and all three left the scan. The three golang.org/x/crypto ssh advisories an earlier override answers clear the same way, so the six statements cover every finding Trivy still returns. Grype reports five further advisories in the docker/docker and containerd modules that Trivy does not; their assessment follows in a separate change.
Proof of effect. On the published standard digest at 8d7eb50, Trivy reports 6 findings without the document and 5 with --vex. The single suppression is GO-2026-5932, and --show-suppressed reports it with our vulnerable_code_not_present justification and our document as the source. The five affected statements suppress nothing by design: an unfixable finding stays visible with the reasoning attached. Each statement also carries the identifiers OSV lists for it, because Grype reports Go module findings under GHSA and GO- spellings and keeps no alias database of its own: with the aliases present Grype 0.118.0 applies a statement to the finding it names, and with them removed it applies nothing.
Attestation is tag-time only. The vex-attest jobs bind the document to the digest the container component published, read from the same container.env artifact the SBOM job reads, so the document, the SBOM and the provenance name one manifest. A document with no statements fails the job rather than attesting a clean bill of health. On merge requests vex-lint renders every variant against a placeholder digest.
Merge order. This MR follows the debug variants' move to scratch and adds no prose about runtime bases, which the Variants section owns.