v0.9.3 — Supply-chain attestation — Sigstore + SBOMs + PEP 740
Supply-chain attestation — closes issue #14. From this release onwards every container image is Sigstore-signed (keyless) and carries CycloneDX + SPDX SBOM attestations; every PyPI distribution carries PEP 740 attestations. No long-lived signing keys; no separate key-management surface.
Note on v0.9.2 — TAGGED BUT NEVER PUBLISHED. v0.9.2 was the first attempt at this slice and shipped only partially: the cosign sign step succeeded (the GHCR image at the v0.9.2 SHA digest was correctly signed), but the SBOM-attestation step failed because
mkdir -p sbomwas missing from the newattestjob. Concurrently, the PyPI publish job stalled on thepypiGitHub environment'srequired_reviewersrule (auto-cleared on previous releases for unclear reasons; this run actually blocked). Tag deleted from remote + local; v0.9.3 is the first successful release of this slice. Same pattern as v0.8.0 → v0.8.1.
Added
- Sigstore keyless image signing. New
attestjob inrelease.ymlruns cosign against the multi-arch GHCR image after publish. Signs by digest (immutable bytes) rather than tag (mutable) — a re-pushed:vX.Y.Zcannot replay a previous signature. Identity = the workflow that ran (Jo-Jo98/ciguard/.github/workflows/release.yml@refs/tags/v0.9.2); short-lived cert issued by Sigstore Fulcio over GitHub Actions OIDC; signature + cert recorded in Sigstore's Rekor public transparency log. - CycloneDX + SPDX SBOMs as cosign attestations on the image. Generated by syft (via
anchore/sbom-action) against the actual built layers — high-fidelity package / version / license manifests. Both formats because consumers vary: CycloneDX is what most SAST / SCA tooling speaks; SPDX is what regulators (US EO 14028, ISO/IEC 5962) ask for. Producing both costs ~10 s and avoids future format conversion. - Python-package CycloneDX SBOM (via
CycloneDX/gh-python-generate-sbom) generated fromrequirements.txtduring the PyPI publish job. Uploaded as a workflow artifact (sbom-python-cyclonedx, 90-day retention). - PEP 740 PyPI attestations. Bumped
pypa/gh-action-pypi-publishto v1.14.0 — every wheel + sdist now ships a Sigstore-signed PEP 740 attestation, visible at https://pypi.org/project/ciguard/#files. Verifies the distribution was built by this workflow, not republished by a compromised maintainer account. - SLSA build provenance via
docker/build-push-action'sprovenance: true+sbom: trueparameters. Adds in-toto SLSA provenance + a buildkit-generated SBOM as image attestations alongside the cosign signature — defence-in-depth from a different angle (build-tool-attested vs. workflow-attested). - README "Verifying releases (Sigstore + SBOMs)" section — copy-paste recipe for
cosign verify+cosign verify-attestation(CycloneDX + SPDX), plus what each layer protects against. The 3-line cosign verify command is the headline: anyone consuming ciguard can prove cryptographically that the image they pulled was built by this workflow at this tag.
Internals
- New
attestjob depends onghcr(image build) + reads itsdigestoutput;trivycontinues to depend onghcronly (independent — runs in parallel withattest). Adds ~2 min to the release flow; releases are rare; the cost is justified by the verifiability gain. - The
pypijob's SBOM is written tosbom/(notdist/) — anything indist/gets uploaded bygh-action-pypi-publishas a release distribution and would break PyPI publishing on first run.
Why this matters strategically
ciguard's narrative is "static security auditor for CI/CD pipelines." From this release, ciguard signs and SBOMs its own releases — and a future ciguard rule will flag pipelines pushing to a registry without doing the same (issue #14's optional follow-up: "eats own dogfood"). Worth its own ship event + blog post.