| diataxis_type | explanation |
|---|
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.
- 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
- 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.
Prerequisite: an authenticated gh CLI.
# Download the release assets
gh release download v0.1.0 --repo USER/REPO
# Verify SLSA build provenance
gh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO
# Verify the SBOM attestation
gh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \
--predicate-type https://cyclonedx.org/bomshasum -a 256 -c rust_template-0.1.0-checksums.txtpublish.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.crate
gh attestation verify rust_template-0.1.0.crate --repo USER/REPOAttestations 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 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 tag
DIGEST=$(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 design
gh 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 signature
cosign 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 (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
| 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.
# Print the full verification result, including the provenance statement
gh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \
--format json | jq '.[0].verificationResult.statement'
# Extract specific fields
gh attestation verify rust_template-0.1.0-linux-amd64 --repo USER/REPO \
--format json | jq '.[0].verificationResult.statement.predicate.buildDefinition'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/REPOAny pipeline consuming release binaries should verify before use:
gh attestation verify "$ARTIFACT" --repo USER/REPO || exit 1For 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-template- 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
- 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
- Rekor transparency log records every attestation signature
- Archive provenance long-term
- Monitor certificates for unexpected issuance
- Document verification in SECURITY.md (canonical commands)
- Provide examples of verification
- Link to tools (
gh attestation, cosign for images)
# Inspect what attestations exist for the artifact
gh 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
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.
All Sigstore signatures (attestations and image signatures) are logged to Rekor:
URL: https://search.sigstore.dev/
Generate reports for audits:
# Verify every asset of every release
gh 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
done
done