Software supply chain
Ship verifiable releases with GitHub artifact attestations
How to add signed build provenance and SBOM attestations to a release, so anyone downstream can check what was built, from which commit, by which workflow.
Problem
A release asset is a promise: this file came from this project. Most consumers have no way to check it. If a binary is rebuilt outside CI, swapped in transit, or built from a different commit than the tag suggests, the file name looks identical and nothing flags the difference. Checksums help only if you trust the place you got the checksum from. Build-pipeline compromises such as the 2020 SolarWinds attack are the reason this problem is taken seriously.
GitHub artifact attestations address this with a signed statement that binds an artifact's digest to its build provenance: the repository, workflow, commit and runner that produced it. The statements follow the in-toto and Sigstore formats. You can verify them with the GitHub CLI, online or offline. An SBOM (software bill of materials) can be attached in the same way, so a consumer can see what is inside the artifact as well as where it came from.
Options and trade-offs
- Do nothing: no friction, and no way for anyone to verify anything.
- Checksums only: catches accidental corruption, but proves nothing about origin if the checksum file sits next to the artifact.
- SBOM only: shows contents, but without a signed link to a build it is just another unverified file.
- Build provenance attestation: a signed statement tying the artifact digest to the workflow run. This is the strongest single step for most projects.
- Provenance plus an SBOM attestation: adds contents transparency on top of origin. Verifying the SBOM requires naming its predicate type explicitly, because verification defaults to provenance.
- Custom predicates: record your own assertions (test results, policy checks) in the in-toto format when provenance and SBOM are not enough.
My recommendation
Attest provenance for every released binary and container image, and add an SBOM attestation when consumers care about contents. Treat verification as part of the release process, not an afterthought: run gh attestation verify in the release workflow's final step so a broken attestation fails the release. Be honest in the README about what this proves. A valid attestation says the artifact came from a particular workflow run. It does not say the workflow was uncompromised or the build reproducible.
Minimal working example
A release workflow for a Java project. It builds a jar, creates an SBOM, attests both, builds and attests a container image by digest, and publishes the release:
name: release
on:
push:
tags: ["v*"]
permissions:
contents: write # create the release
id-token: write # sign with the workflow's OIDC identity
attestations: write # store the attestation
packages: write # push the image
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
distribution: temurin
java-version: "21"
- name: Build
run: ./gradlew --no-daemon build
- name: Generate SBOM
uses: anchore/sbom-action@v0
with:
path: .
format: spdx-json
output-file: sbom.spdx.json
- name: Attest build provenance (jar)
uses: actions/attest-build-provenance@v2
with:
subject-path: build/libs/*.jar
- name: Attest SBOM
uses: actions/attest-sbom@v2
with:
subject-path: build/libs/*.jar
sbom-path: sbom.spdx.json
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push image
id: push
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
- name: Attest image provenance (by digest)
uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
- name: Publish release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: gh release create "${{ github.ref_name }}" build/libs/*.jar sbom.spdx.json --generate-notes
Verify as a consumer:
# Provenance (the default)
gh attestation verify build/libs/app-1.0.0.jar --repo OWNER/REPO
# SBOM: name the predicate type explicitly
gh attestation verify build/libs/app-1.0.0.jar --repo OWNER/REPO \
--predicate-type https://spdx.dev/Document/v2.3
# Container image
gh attestation verify oci://ghcr.io/OWNER/REPO:v1.0.0 --repo OWNER/REPO
Test it: release a tag, run the three commands against the published assets, then change one byte of the jar and confirm verification fails.
Checklist
- Grant the job
id-token: writeandattestations: write. Addpackages: writewhen pushing images. - Attest container images by digest, not by tag, and leave the tag out of
subject-name. - Pin third-party actions to a full commit SHA and let Dependabot update them. The attest actions move between major versions, so check the current release before copying versions from any article.
- Verify the artifact in CI before publishing, and document the verification command in the README.
- Publish the SBOM next to the artifact, and tell users to pass the SBOM predicate type when verifying it.
- Practice offline verification (download the attestation bundle and the trusted root with the CLI) for environments without internet access.
- State plainly in the README what an attestation does and does not prove.
Where this doesn't apply
If you only ship source and no built artifacts, there is little to attest. Attestations for private and internal repositories need GitHub Enterprise Cloud, and the feature is not available on GitHub Enterprise Server, so check your plan first. Fully air-gapped environments need their own trust anchors.
One honest limitation
Provenance is necessary but not sufficient. A signed statement that a workflow built a file does not make the build hermetic or reproducible, and a compromised workflow produces a perfectly valid attestation. Higher assurance depends on build isolation and organizational policy, which the SLSA levels describe.
Sources
- Artifact attestations — https://docs.github.com/en/actions/concepts/security/artifact-attestations
- actions/attest-build-provenance — https://github.com/actions/attest-build-provenance
- actions/attest-sbom — https://github.com/actions/attest-sbom
- Verifying attestations offline — https://docs.github.com/en/actions/security-for-github-actions/using-artifact-attestations/verifying-attestations-offline
- SLSA specification, build levels — https://slsa.dev/spec/v1.0/levels
---