Attestations let you prove cryptographically where your artefacts come from: who built them, from which code, with which workflow. They are a key part of supply chain security.
What you will learn
- Understand attestations and what they prove cryptographically
- Tell apart a provenance attestation (SLSA) and an SBOM attestation
- Generate an attestation for an artefact or a Docker image
- Attest an SBOM alongside the provenance
- Know where GitHub and Sigstore keep the attestations
What is an attestation?
An attestation is a signed document certifying information about an artefact:
{ "_type": "https://in-toto.io/Statement/v1", "subject": [ { "name": "my-app", "digest": { "sha256": "abc123..." } } ], "predicateType": "https://slsa.dev/provenance/v1", "predicate": { "buildDefinition": { "buildType": "https://actions.github.io/buildtypes/workflow/v1" }, "runDetails": { "builder": { "id": "https://github.com/actions/runner" } } }}What the attestation proves:
| Information | Value |
|---|---|
| Artefact | SHA256 hash of the artefact |
| Builder | GitHub Actions runner |
| Repository | owner/repo |
| Commit | The commit SHA |
| Workflow | File and reference |
| Signer | Sigstore (Fulcio/Rekor) |
Types of attestation
GitHub Actions produces two standard attestation types, provenance and SBOM, and allows custom attestations.
Provenance attestation (SLSA)
Proves how the artefact was built:
- From which source code
- With which workflow
- With which build parameters
SBOM attestation
Proves what the artefact contains:
- The list of dependencies
- The exact versions
- The licences
Custom attestation
You can add your own information:
- Test results
- Security scan results
- Business metadata
Generating an attestation
The official actions/attest-build-provenance action generates and signs the
attestation. The workflow needs three permissions: id-token, contents
and attestations.
With actions/attest-build-provenance
This action produces a provenance attestation for any artefact file.
name: Build and Attest
on: push: branches: [main]
permissions: id-token: write # To sign with Sigstore contents: read attestations: write # To publish the attestation
jobs: build: runs-on: ubuntu-24.04 steps: - name: Check out the code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Build the artefact run: | npm ci npm run build tar -czf dist.tar.gz dist/
- name: Generate the attestation uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-path: dist.tar.gzFor a Docker image
For an image, the attestation targets the published digest and can be attached directly to the registry.
jobs: build: runs-on: ubuntu-24.04 permissions: id-token: write contents: read attestations: write packages: write steps: - name: Check out the code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Log in to GHCR uses: docker/login-action@06fb636fac595d6fb4b28a5dfcb21a6f5091859c # v4.5.0 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push the image id: build uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 with: push: true tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
- name: Generate the attestation uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: trueSBOM attestation
Generate an SBOM (Software Bill of Materials) then attest it, to prove the exact content of the artefact:
jobs: sbom: runs-on: ubuntu-24.04 permissions: id-token: write contents: read attestations: write steps: - name: Check out the code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Generate the SBOM uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 id: sbom with: format: spdx-json output-file: sbom.spdx.json
- name: Attest the SBOM uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 with: subject-path: dist.tar.gz sbom-path: sbom.spdx.jsonWhere are the attestations stored?
A generated attestation is kept in two places, which makes it both queryable and tamper-evident.
The GitHub Attestations API
Attestations are stored in GitHub and reachable through:
- The REST API:
GET /repos/{owner}/{repo}/attestations/{digest} - The CLI:
gh attestation verify
The Sigstore transparency log
Attestations are also recorded in Rekor, the Sigstore transparency log. That guarantees they cannot be altered after the fact.
A complete workflow
Here is a full release workflow: build, SBOM, double attestation (provenance and SBOM), then publication.
name: Build, Attest and Release
on: push: tags: - 'v*'
permissions: id-token: write contents: write attestations: write packages: write
jobs: build: runs-on: ubuntu-24.04 outputs: digest: ${{ steps.build.outputs.digest }} steps: - name: Check out the code uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false
- name: Build the artefact id: build env: REF_NAME: ${{ github.ref_name }} run: | npm ci npm run build tar -czf "my-app-$REF_NAME.tar.gz" dist/ echo "digest=$(sha256sum "my-app-$REF_NAME.tar.gz" | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
- name: Generate the SBOM uses: anchore/sbom-action@e22c389904149dbc22b58101806040fa8d37a610 # v0.24.0 with: format: spdx-json output-file: sbom.spdx.json
- name: Attest the provenance uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-path: my-app-${{ github.ref_name }}.tar.gz
- name: Attest the SBOM uses: actions/attest-sbom@c604332985a26aa8cf1bdc465b92731239ec6b9e # v4.1.0 with: subject-path: my-app-${{ github.ref_name }}.tar.gz sbom-path: sbom.spdx.json
- name: Create the release uses: softprops/action-gh-release@3d0d9888cb7fd7b750713d6e236d1fcb99157228 # v3.0.2 with: files: | my-app-${{ github.ref_name }}.tar.gz sbom.spdx.jsonGood practices
A few reflexes make sure your attestations genuinely secure the delivery chain.
1. Always attest the releases
Every distributed artefact must carry a provenance attestation; without one, the consumer cannot verify where it came from.
2. Attest the Docker images
For an image, push-to-registry: true attaches the attestation directly to the
image in the registry.
- uses: actions/attest-build-provenance@0f67c3f4856b2e3261c31976d6725780e5e4c373 # v4.1.1 with: subject-name: ghcr.io/${{ github.repository }} subject-digest: ${{ steps.build.outputs.digest }} push-to-registry: true # Attaches the attestation to the image3. Generate an SBOM as well
The provenance attestation says how the artefact was built; the SBOM says what it contains. The two are complementary.
4. Verify before deployment
An attestation is only worth something when it is verified on the consumer side. Before deploying an artefact, validate its provenance: see Verifying attestations.
Key points
- An attestation is a signed document proving the origin of an artefact: source code, workflow, builder.
- The provenance attestation (SLSA) says how the artefact was built; the SBOM attestation says what it contains.
actions/attest-build-provenanceandactions/attest-sbomrequire theid-token: writeandattestations: writepermissions.- Attestations are signed through Sigstore and recorded in the Rekor transparency log, so they cannot be altered afterwards.
- An attestation only protects you when it is verified before deployment.
For the full reference, see the official documentation on artifact attestations.
Next steps
- Verifying attestations: the consumer side, the only step that turns an attestation into an actual guarantee.
- Security checklist: the recap that places attestation among the other controls to tick before publishing.
- OpenSSF Scorecard: the check that measures whether your releases are signed, among eighteen others.