SLSA provenance is the difference between "we built this somewhere" and "we built this, in this workflow, from this commit, at this time, and can prove it." This tutorial walks you through generating SLSA v1.0 provenance for a Go binary using the official slsa-github-generator, validating it in a downstream consumer CI job with slsa-verifier v2.5, and setting up a release gate that blocks artifacts whose provenance does not match the expected builder and source repository. Prerequisites: a GitHub Actions repo with id-token: write permission, gh CLI 2.40+, and 30 minutes. If you have already shipped Cosign signing, the incremental work here is small but the assurance jump is significant.
What is SLSA provenance?
SLSA provenance is a signed attestation describing how an artifact was built: the builder identity, source repo and commit, build parameters, and materials consumed. SLSA v1.0 defines three increasing levels of assurance — L1 is documented, L2 is hosted and signed, L3 is non-falsifiable.
For most orgs, SLSA L3 via the slsa-github-generator hosted workflow is the right target. It runs in an isolated GitHub-hosted builder you cannot tamper with, and signs provenance with a short-lived OIDC cert from Fulcio.
How do I generate provenance for a Go binary?
Call the reusable slsa-github-generator workflow from your release job. It produces a signed intoto.jsonl attestation alongside the binary and uploads both to the release.
jobs:
build:
uses: slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@v2.0.0
with:
go-version: '1.22'
config-file: .slsa-goreleaser.yml
permissions:
id-token: write
contents: write
actions: read
The hosted builder will not accept secrets — by design. If your build needs credentials, restructure so the SLSA-generated binary is the input to a later deploy job, and keep secrets out of the reproducible build step.
How do I verify provenance?
Install slsa-verifier v2.5 and call verify-artifact with the expected source URI and builder ID. Verification fails closed on any mismatch.
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@v2.5.1
slsa-verifier verify-artifact \
--provenance-path api.intoto.jsonl \
--source-uri github.com/acme/api \
--builder-id "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/builder_go_slsa3.yml@refs/tags/v2.0.0" \
api-linux-amd64
# Verified signature against tlog entry index 89421033 at URL: https://rekor.sigstore.dev/api/v1/log/entries/...
# PASSED: SLSA verification passed
Pin --builder-id to a specific tag, not a branch. An attacker who compromises main of the generator repo could otherwise mint provenance with a builder-ID you happily accept.
How do I pull provenance from a GitHub Release?
Use gh release download to grab both the artifact and its .intoto.jsonl sidecar in one step. Consumers rarely need to talk to Rekor directly — the sidecar plus local verification is sufficient.
gh release download v1.4.0 \
--repo acme/api \
--pattern 'api-linux-amd64' \
--pattern '*.intoto.jsonl' \
--dir ./downloaded
ls downloaded/
# api-linux-amd64 api-linux-amd64.intoto.jsonl
The .intoto.jsonl file contains a DSSE envelope with a base64-encoded in-toto v1.0 statement and a Fulcio-issued certificate. You do not need to parse it yourself — slsa-verifier handles it.
How do I gate deployments on provenance?
Run slsa-verifier in a deploy job and fail the job if verification does not return exit 0. Combine with a branch protection rule that requires the "provenance-verified" check before merging to release/*.
- name: Verify SLSA provenance
run: |
slsa-verifier verify-artifact \
--provenance-path "${{ steps.download.outputs.bom }}" \
--source-uri "github.com/${{ github.repository }}" \
--builder-id "${BUILDER_ID}" \
"${{ steps.download.outputs.artifact }}"
In a monorepo with multiple artifacts, loop over artifacts in a matrix job. Any failed leg fails the whole deploy — do not summarize with continue-on-error: true.
How do I handle verification in air-gapped environments?
Pre-download the Rekor log snapshot and the Fulcio roots, then pass them to slsa-verifier with the --trusted-root flag. Verification still works offline as long as the provenance was issued before the snapshot.
curl -sL https://tuf-repo-cdn.sigstore.dev/targets/trusted_root.json \
-o trusted_root.json
slsa-verifier verify-artifact \
--trusted-root trusted_root.json \
--provenance-path api.intoto.jsonl \
--source-uri github.com/acme/api \
api-linux-amd64
Rotate the trusted root quarterly. Sigstore's TUF root is re-signed regularly and an expired root silently degrades you to an unverified state if you forget.
How Safeguard Helps
Safeguard ingests SLSA provenance during SBOM upload and uses it as first-class evidence in its supply chain graph. Griffin AI cross-references the builder ID and source URI against your configured policy — flagging artifacts built from unexpected forks, unpinned generator tags, or workflows outside your org. Provenance data feeds into Safeguard's reachability engine: a vulnerable transitive dep that shipped without provenance is ranked higher than one with a verified L3 attestation from your hardened builder. Policy gates can require provenance-verified status before promotion to production, and the evidence is stored alongside your SBOMs for audit. Ship attested builds, and Safeguard turns the attestations into deployment gates automatically.