Attach Build Provenance to Release Artifacts
Generate a signed SLSA provenance attestation in GitHub Actions and verify it before deploying the container image.
Task
Prove that a deployed image was built by your CI workflow from a specific commit, not on a laptop or by an unknown pipeline.
When to use this
A release image is promoted from staging to production by tag. There is no way to confirm the image came from the protected main branch, so a compromised registry credential could let an attacker push a look-alike image with the same tag.
Prerequisites
- GitHub Actions with a container registry such as GHCR
- Workflow permissions id-token: write, attestations: write, packages: write
- GitHub CLI 2.49 or newer for verification
Steps
1. Build and push the image by digest, then attest that digest in the same workflow.
permissions:
contents: read
id-token: write
attestations: write
packages: write
steps:
- uses: actions/checkout@v4
- uses: docker/build-push-action@v6
id: build
with:
push: true
tags: ghcr.io/acme/payments-api:${{ github.sha }}
- uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/acme/payments-api
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
Expected output: The registry stores an attestation linked to the image digest and the workflow identity.
2. Verify the attestation locally before promoting the image.
gh attestation verify oci://ghcr.io/acme/payments-api:latest --owner acmeExpected output: Verification succeeds and reports the workflow, repository, and commit that produced the image.
3. Make the deploy job verify provenance and fail closed on any error.
- name: Verify provenance
run: gh attestation verify oci://ghcr.io/acme/payments-api:${{ github.sha }} --owner acme
- name: Deploy
run: kubectl set image deployment/payments-api api=ghcr.io/acme/payments-api:${{ github.sha }}
Expected output: Deployment stops if the attestation is missing, invalid, or bound to a different repository.
4. Test the negative case by verifying an image that was built outside the workflow.
gh attestation verify oci://ghcr.io/acme/debug-tools:dev --owner acmeExpected output: The command exits non-zero with no attestations found, proving the gate rejects unattested images.
Troubleshooting
Problem
Verification reports no attestations found
Solution
Confirm push-to-registry is true, the subject digest matches the image digest, and the attestation was created in the same repository that owns the image.
Problem
The workflow cannot request an OIDC token
Solution
Add id-token: write at the job level; organization policy may also restrict which workflows can request tokens.
Limitations
- Provenance proves how and where an artifact was built, not that the source code is free of vulnerabilities.
- Verification needs network access to the GitHub attestation API and trusts GitHub as the identity provider.
Provenance answers a narrow question
Build provenance answers: which workflow, repository, and commit produced this exact digest? It does not scan the image or judge code quality. That narrow guarantee is enough to block a class of supply-chain attacks where someone pushes an image that looks like a release but was built elsewhere.
Attest the digest
Tags are mutable; digests are not. Build the image, read the digest output, and attest that digest. The attestation is stored in the registry alongside the image, so verification travels with the artifact. Grant only the permissions needed: id-token: write for the OIDC identity, attestations: write for the attestation, and packages: write for the push.
- uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/acme/payments-api
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
Verify at the gate
Verification belongs in the deploy job, not in a wiki page. Run gh attestation verify against the image reference and owner before any kubectl set image. If the command fails, stop. Treat a missing attestation as a failed verification rather than skipping the check for convenience.
Keep the chain intact
Protect the main branch, require reviews, and pin third-party actions by commit SHA so the workflow itself cannot be altered silently. Record the verified digest in the deployment record. When promoting between environments, promote the digest, not a rebuilt image, so the artifact that was tested is the artifact that runs.