Signed Releases
Overview
Section titled “Overview”Every release artifact carries cryptographic attestations, and nothing publishes unverified: a fail-closed verification job runs before the GitHub Release exists.
Workflows:
.github/workflows/release.yml- SLSA build provenance and CycloneDX SBOM attestations on every release binary, verified fail-closed before the release is published.github/workflows/publish.yml- provenance attestation on the.cratearchive that crates.io actually serves.github/workflows/pipeline.yml- container image signing and attestation via the centralizedzircote/.githubsigner workflow (SLSA Build L3), then fail-closed verification
The canonical verification commands live in SECURITY.md. This document explains the architecture and expands on each artifact type.
Why Attest Releases?
Section titled “Why Attest Releases?”- Authenticity: Verify artifacts were built by this repository’s workflows
- Integrity: Detect tampering or corruption
- Non-repudiation: The attestation binds the artifact to the exact commit, workflow, and run
- Compliance: Meet supply chain security requirements
GitHub Artifact Attestations (Release Binaries)
Section titled “GitHub Artifact Attestations (Release Binaries)”How It Works
Section titled “How It Works”- Tag pushed -
release.ymltriggers; binary name and version are resolved fromcargo metadata - Build binaries - 5 platform targets, named
{bin}-{version}-{platform}(e.g.rust_template-0.1.0-linux-amd64) - Attest provenance -
actions/attest-build-provenanceattaches SLSA build provenance to each binary at build time - Generate + attest SBOM - a CycloneDX SBOM is generated (
anchore/sbom-action) and bound to every binary viaactions/attest-sbom - Verify fail-closed - a dedicated job runs
gh attestation verify(provenance and SBOM) against every artifact; any failure blocks the release - Publish release - binaries, the SBOM, and a single
{bin}-{version}-checksums.txtfile are attached to the GitHub Release
A tag publishes nothing unattested. Test and cargo-audit gates also run in the same workflow, because tags are not guaranteed to point at CI-green commits.
Verifying Release Binaries
Section titled “Verifying Release Binaries”Prerequisite: an authenticated gh CLI.
# Download the release assetsgh release download v0.1.0 --repo USER/REPO
# Verify SLSA build provenancegh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO
# Verify the SBOM attestationgh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \ --predicate-type https://cyclonedx.org/bomVerifying Checksums
Section titled “Verifying Checksums”shasum -a 256 -c rust_template-0.1.0-checksums.txtVerifying the Published Crate
Section titled “Verifying the Published Crate”publish.yml downloads the .crate that crates.io serves, byte-compares it against the locally packaged archive (a mismatch fails the workflow), and attests it — the attestation covers the registry bytes, not a local rebuild:
curl -fsSL -A 'release-check' \ -O https://static.crates.io/crates/rust_template/rust_template-0.1.0.crategh attestation verify rust_template-0.1.0.crate --repo USER/REPOKeyless Signing
Section titled “Keyless Signing”Attestations are signed keyless via Sigstore:
- No private keys to manage
- Uses OIDC identity (GitHub Actions)
- Transparency log (Rekor) for auditability
- Certificate from Fulcio CA
Benefits:
- No key rotation needed
- No key compromise risk
- Publicly verifiable
- Auditable via transparency log
Container Image Attestations
Section titled “Container Image Attestations”Container images are not signed by this repository. They are signed and attested by the centralized signer workflow zircote/.github/.github/workflows/sign-and-attest.yml, then verified fail-closed by docker-verify in pipeline.yml. Under SLSA Build L3 the signing identity is the central workflow, not this repo — so verification must assert both where the build ran (--repo) and who signed (--signer-workflow):
# Resolve the digest for a tagDIGEST=$(gh api 'users/USER/packages/container/REPO/versions?per_page=20' \ --jq '[.[] | select((.metadata.container.tags // []) | index("<tag>"))][0].name')
# SLSA provenance — --repo alone fails by designgh attestation verify "oci://ghcr.io/USER/REPO@${DIGEST}" \ --repo USER/REPO \ --signer-workflow zircote/.github/.github/workflows/sign-and-attest.yml \ --predicate-type https://slsa.dev/provenance/v1
# Keyless signaturecosign verify "ghcr.io/USER/REPO@${DIGEST}" \ --certificate-identity-regexp '^https://github.com/zircote/\.github/\.github/workflows/sign-and-attest\.yml@.*$' \ --certificate-oidc-issuer https://token.actions.githubusercontent.comThe central signer also attaches SBOM and vulnerability-report attestations; see SECURITY.md for those commands.
SLSA Provenance
Section titled “SLSA Provenance”What is SLSA?
Section titled “What is SLSA?”SLSA (Supply chain Levels for Software Artifacts) is a framework for ensuring software supply chain integrity.
Levels:
- SLSA 1: Documentation of build process
- SLSA 2: Version control + build service
- SLSA 3: Hardened builds (non-falsifiable provenance)
- SLSA 4: Hermetic, reproducible builds
Who Signs What
Section titled “Who Signs What”| Artifact | Attested by | Verify with |
|---|---|---|
| Release binaries + SBOM | This repo’s release.yml | gh attestation verify <file> --repo USER/REPO |
Published .crate | This repo’s publish.yml | gh attestation verify <crate> --repo USER/REPO |
| Container images | Central zircote/.github signer (SLSA Build L3) | gh attestation verify oci://... --repo USER/REPO --signer-workflow ... |
No --signer-workflow flag is needed for binaries and crates — they are attested by this repository’s own workflows.
Inspecting Provenance
Section titled “Inspecting Provenance”# Print the full verification result, including the provenance statementgh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \ --format json | jq '.[0].verificationResult.statement'
# Extract specific fieldsgh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \ --format json | jq '.[0].verificationResult.statement.predicate.buildDefinition'Integration Examples
Section titled “Integration Examples”Docker
Section titled “Docker”Verify a binary before adding it to an image:
# Verify provenance before adding to image (gh CLI in the build stage)RUN gh release download v0.1.0 --repo USER/REPO \ --pattern 'rust_template-0.1.0-linux-amd64' && \ gh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPOCI Consumers
Section titled “CI Consumers”Any pipeline consuming release binaries should verify before use:
gh attestation verify "$ARTIFACT" --repo USER/REPO || exit 1Advanced Configuration
Section titled “Advanced Configuration”Custom Signing Keys
Section titled “Custom Signing Keys”For organizations with existing PKI, GPG signatures can be layered on top of attestations:
- name: Import GPG key run: echo "${{ secrets.GPG_PRIVATE_KEY }}" | gpg --import
- name: Sign with GPG run: | for file in *; do gpg --detach-sign --armor "$file" doneVerify GPG:
gpg --verify rust-template.asc rust-templateSecurity Best Practices
Section titled “Security Best Practices”1. Minimize Attack Surface
Section titled “1. Minimize Attack Surface”- Use official actions with commit SHA pinning (enforced by the
pin-checkCI gate) - Limit permissions to minimum required (
id-token: writeandattestations: writeonly on jobs that attest) - Avoid secrets in logs or artifacts
2. Verify Everything
Section titled “2. Verify Everything”- Verify dependencies before building (
cargo-deny,cargo-auditgates) - Verify artifacts before they publish — the release workflow’s verify job is fail-closed
- Verify on the consuming side — in-pipeline success is necessary, not sufficient
3. Audit Trail
Section titled “3. Audit Trail”- Rekor transparency log records every attestation signature
- Archive provenance long-term
- Monitor certificates for unexpected issuance
4. User Education
Section titled “4. User Education”- Document verification in SECURITY.md (canonical commands)
- Provide examples of verification
- Link to tools (
gh attestation, cosign for images)
Troubleshooting
Section titled “Troubleshooting”gh attestation verify Fails
Section titled “gh attestation verify Fails”# Inspect what attestations exist for the artifactgh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO --format jsonCommon issues:
- Wrong
--repo(must be the repository whose workflow attested the artifact) - Missing
--signer-workflowfor container images (they are signed by the central workflow;--repoalone fails by design) - Wrong
--predicate-type(SBOM attestations needhttps://cyclonedx.org/bom; image provenance needshttps://slsa.dev/provenance/v1) - Artifact was modified after download (checksum it against
{bin}-{version}-checksums.txt) - Unauthenticated
ghCLI
Release Was Not Published
Section titled “Release Was Not Published”If the verify job fails, the release is intentionally never created — that is the fail-closed design working. Check the Verify Attestations job logs, fix the cause, and re-release with a new tag. Never re-run release.yml against an existing tag: builds are not reproducible and re-publishing would overwrite released assets with different bytes.
Monitoring & Compliance
Section titled “Monitoring & Compliance”Rekor Transparency Log
Section titled “Rekor Transparency Log”All Sigstore signatures (attestations and image signatures) are logged to Rekor:
URL: https://search.sigstore.dev/
Compliance Reports
Section titled “Compliance Reports”Generate reports for audits:
# Verify every asset of every releasegh release list --repo USER/REPO --json tagName -q '.[].tagName' | while read -r tag; do echo "Release: $tag" gh release download "$tag" --repo USER/REPO --dir "audit/$tag" for f in audit/$tag/*; do case "$f" in *checksums.txt) continue ;; esac gh attestation verify "$f" --repo USER/REPO donedone