Introduction
Code signing has long been a cornerstone of software security. When you verify a signature, you know who signed an artifact. But knowing who signed something does not tell you how it was built, where it was built, or what source code went into it. A maintainer could sign a binary that was compiled on a compromised laptop with injected dependencies — and the signature would still verify perfectly.
This is the gap that artifact provenance fills. Provenance is a verifiable record of how a software artifact was produced. It captures the build platform, the source repository, the entry point, the build parameters, and the dependencies involved. Combined with signatures, provenance gives consumers a complete picture: not just “who vouches for this artifact,” but “what process actually created it.”
In this guide, we will explore the two most important frameworks in this space — SLSA (Supply-chain Levels for Software Artifacts) and in-toto — and walk through practical implementation in modern CI/CD pipelines. Whether you are a platform engineer securing your build infrastructure or a developer trying to understand what SLSA Level 3 actually requires, this post will give you the technical depth you need.
What Is Artifact Provenance?
Artifact provenance is metadata that describes the origin and build process of a software artifact. Think of it as a build receipt: a structured, signed document that answers three fundamental questions:
- Where was the artifact built? (Which build platform, which runner, which environment?)
- How was it built? (What build command, what configuration, what entry point?)
- From what was it built? (Which source repository, which commit, which dependencies?)
Provenance vs. Signatures
Signatures and provenance are complementary but serve different purposes:
- A signature binds an identity to an artifact. It proves that a specific key holder approved or produced the artifact. It says nothing about the build process.
- Provenance binds a build process to an artifact. It proves that a specific source revision was transformed into the artifact by a specific build platform using specific parameters.
You need both. A signature without provenance is a trust-me statement. Provenance without a signature is an unverified claim. Together, they provide tamper evidence, auditability, and a foundation for automated policy enforcement.
Why Provenance Matters
Provenance addresses several critical supply chain risks:
- Tamper evidence: If an attacker modifies an artifact after it was built, the provenance will not match. If they modify the build process, the provenance will reflect a different builder or configuration than expected.
- Auditability: When a vulnerability is discovered, provenance lets you trace exactly which source commit and build configuration produced the affected artifact.
- Compliance: Frameworks like NIST SSDF and executive orders on software supply chain security increasingly require provenance as a baseline control.
- Automated policy: Admission controllers and deployment gates can verify provenance programmatically, enforcing that only artifacts built by trusted platforms from approved repositories are deployed.
The SLSA Framework
SLSA (pronounced “salsa”) is a security framework originally developed at Google and now maintained by the OpenSSF. It defines a maturity model for supply chain security, with provenance at its core. SLSA does not prescribe specific tools — it defines requirements that tools and platforms must meet.
The SLSA Build Model
SLSA models the software supply chain as a simple pipeline:
Source → Build Platform → Artifact
Each stage has distinct threats. The source can be tampered with (malicious commits, compromised VCS). The build platform can be compromised (modified build scripts, injected dependencies). The artifact can be tampered with after the build (registry poisoning, man-in-the-middle). SLSA addresses each of these through increasingly strict requirements at each level.
SLSA Levels (v1.0)
SLSA v1.0 defines four build levels, each building on the previous:
Build Level 1 — Provenance Exists
- The build process generates provenance describing how the artifact was produced.
- The provenance format follows the SLSA specification.
- The provenance does not need to be signed or generated by the build platform itself.
- Threat addressed: Provides a baseline for auditability. Does not prevent tampering.
Build Level 2 — Hosted Build, Signed Provenance
- The build runs on a hosted build platform (not a developer workstation).
- The provenance is signed by the build platform, not by the project maintainer.
- The provenance is generated by the build service itself and cannot be modified by the build tenant.
- Threat addressed: Prevents the build tenant from falsifying provenance. Consumers can verify the build service identity.
Build Level 3 — Hardened Build Platform
- The build platform provides strong isolation between build tenants (e.g., ephemeral, sandboxed environments).
- The provenance is unforgeable: even a compromised build tenant cannot create false provenance for another project.
- Builds run in hermetic environments with controlled, declared dependencies.
- Threat addressed: Prevents a compromised build from affecting other projects. Prevents builds from using undeclared dependencies.
Build Level 4 — Two-Party Review and Hermetic Builds
- All source changes require two-party review before being accepted into the build.
- Builds are fully hermetic: all dependencies are declared and fetched in a reproducible manner.
- The build platform is fully isolated and auditable.
- Threat addressed: Prevents a single insider from injecting malicious code that gets built and distributed.
SLSA Provenance Specification
SLSA defines a specific provenance format that must include:
- Builder identity: Which build platform produced the artifact (e.g., GitHub Actions, Google Cloud Build).
- Build configuration: The entry point and parameters for the build (e.g., the workflow file, the build command).
- Source reference: The source repository and commit digest that was built.
- Materials: A list of all inputs to the build, including dependencies and their digests.
- Metadata: Timestamps, invocation IDs, and other build metadata.
This provenance is expressed as a structured JSON document, specifically as an in-toto attestation — which brings us to the next section.
The in-toto Attestation Framework
in-toto is a framework for securing software supply chains through cryptographically signed metadata. While SLSA defines what provenance should contain and what security level it provides, in-toto defines the data format and signing mechanism used to represent and verify that provenance.
in-toto Layout and Link Metadata
In the original in-toto model, a supply chain is described by two types of metadata:
- Layout: A document signed by the project owner that defines the expected supply chain steps, who is authorized to perform each step, and what inspection rules should be applied during verification.
- Link metadata: Signed evidence produced by each step in the supply chain, recording the materials (inputs) and products (outputs) of that step.
This model is powerful for multi-step supply chains where you need to verify that step A produced output X, which was then consumed by step B to produce output Y, with each step performed by an authorized actor.
The in-toto Attestation Format
The modern in-toto attestation format (ITE-6) generalizes the original model into a flexible, extensible framework. An attestation has three layers:
1. Statement: The outer envelope that binds a predicate to one or more subjects.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "my-artifact",
"digest": {
"sha256": "a1b2c3d4e5f6..."
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": { ... }
}
2. Predicate: The actual metadata about the artifact. Different predicate types serve different purposes:
https://slsa.dev/provenance/v1— SLSA provenance (build information)https://spdx.dev/Document— SBOM in SPDX formathttps://cyclonedx.org/bom— SBOM in CycloneDX formathttps://in-toto.io/attestation/vulns— Vulnerability scan results
3. Subject: One or more artifact references, each identified by a name and a set of cryptographic digests. This binds the predicate to specific artifacts.
DSSE (Dead Simple Signing Envelope)
in-toto attestations are wrapped in a DSSE (Dead Simple Signing Envelope) for signing. DSSE solves several problems with previous signing approaches:
{
"payloadType": "application/vnd.in-toto+json",
"payload": "<base64-encoded statement>",
"signatures": [
{
"keyid": "...",
"sig": "<base64-encoded signature>"
}
]
}
DSSE signs over the payload type and payload together (using a PAE — Pre-Authentication Encoding), preventing confusion attacks where a signature for one payload type is reused for another. It supports multiple signatures, enabling multi-party signing scenarios.
How in-toto Relates to SLSA
The relationship is straightforward: SLSA provenance is an in-toto predicate type. When a SLSA-compliant build platform generates provenance, it produces an in-toto attestation with predicateType: https://slsa.dev/provenance/v1, signs it with DSSE, and associates it with the built artifact via the subject field. SLSA defines the requirements and threat model; in-toto provides the data format and verification framework.
Generating Provenance in CI/CD
Let us walk through practical implementations in the most common CI/CD platforms.
GitHub Actions: slsa-github-generator
The slsa-framework/slsa-github-generator project provides reusable workflows that generate SLSA Level 3 provenance on GitHub Actions. The provenance is generated by a hosted, isolated workflow that the build tenant cannot tamper with.
For generic artifacts:
name: SLSA Provenance for Generic Artifacts
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
outputs:
digests: ${{ steps.hash.outputs.digests }}
steps:
- uses: actions/checkout@v4
- name: Build artifact
run: |
go build -o my-binary ./cmd/app
- name: Generate subject digest
id: hash
run: |
DIGEST=$(sha256sum my-binary | base64 -w0)
echo "digests=$DIGEST" >> "$GITHUB_OUTPUT"
- uses: actions/upload-artifact@v4
with:
name: my-binary
path: my-binary
provenance:
needs: [build]
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0
with:
base64-subjects: ${{ needs.build.outputs.digests }}
upload-assets: true
For container images:
name: SLSA Provenance for Container Images
on:
push:
tags:
- "v*"
jobs:
build:
runs-on: ubuntu-latest
outputs:
image: ${{ steps.build.outputs.image }}
digest: ${{ steps.build.outputs.digest }}
permissions:
packages: write
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
provenance:
needs: [build]
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2.1.0
with:
image: ${{ needs.build.outputs.image }}
digest: ${{ needs.build.outputs.digest }}
registry-username: ${{ github.actor }}
secrets:
registry-password: ${{ secrets.GITHUB_TOKEN }}
GitHub Artifact Attestations
GitHub now offers native artifact attestations through actions/attest-build-provenance. This is simpler than the SLSA generator and produces Sigstore-signed attestations stored in GitHub’s attestation API.
name: Build and Attest
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
attestations: write
steps:
- uses: actions/checkout@v4
- name: Build binary
run: go build -o my-binary ./cmd/app
- name: Attest build provenance
uses: actions/attest-build-provenance@v2
with:
subject-path: my-binary
- name: Attest container image
uses: actions/attest-build-provenance@v2
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.push.outputs.digest }}
push-to-registry: true
GitLab CI: Generating Provenance Metadata
GitLab does not yet have a native SLSA provenance generator at the same maturity as GitHub’s, but you can generate and sign provenance metadata using the SLSA framework tools directly.
stages:
- build
- provenance
build:
stage: build
image: golang:1.22
script:
- go build -o my-binary ./cmd/app
- sha256sum my-binary > checksums.txt
artifacts:
paths:
- my-binary
- checksums.txt
generate-provenance:
stage: provenance
image: ghcr.io/slsa-framework/slsa-generator-generic:v2.1.0
needs: [build]
script:
- |
cat > provenance.json <<PROV
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "my-binary",
"digest": {
"sha256": "$(sha256sum my-binary | awk '{print $1}')"
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://gitlab.com/gitlab-ci",
"externalParameters": {
"repository": "${CI_PROJECT_URL}",
"ref": "${CI_COMMIT_SHA}"
}
},
"runDetails": {
"builder": {
"id": "https://gitlab.com/${CI_PROJECT_PATH}/-/runners/${CI_RUNNER_ID}"
},
"metadata": {
"invocationId": "${CI_PIPELINE_URL}",
"startedOn": "${CI_PIPELINE_CREATED_AT}"
}
}
}
}
PROV
- cosign attest-blob --predicate provenance.json --type slsaprovenance my-binary
artifacts:
paths:
- provenance.json
Storing Provenance in OCI Registries
For container images, provenance attestations are typically stored alongside the image in the OCI registry using the Cosign attestation model. The attestation is stored as a separate OCI artifact linked to the image by digest:
# Attach provenance to a container image in the registry
cosign attest --predicate provenance.json \
--type slsaprovenance \
--key cosign.key \
ghcr.io/myorg/myimage@sha256:abc123...
# For keyless signing with Sigstore
cosign attest --predicate provenance.json \
--type slsaprovenance \
--yes \
ghcr.io/myorg/myimage@sha256:abc123...
This approach leverages the OCI distribution spec’s referrers API, which allows clients to discover attestations associated with a given image manifest. Tools like cosign and crane can then fetch and verify these attestations during deployment.
Verifying Provenance
Generating provenance is only half the story. Consumers — whether human operators or automated systems — must verify provenance before trusting an artifact.
slsa-verifier CLI
The slsa-verifier tool verifies SLSA provenance generated by trusted builders (currently GitHub Actions-based builders).
# Verify a generic artifact
slsa-verifier verify-artifact my-binary \
--provenance-path my-binary.intoto.jsonl \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
# Verify a container image
slsa-verifier verify-image ghcr.io/myorg/myimage@sha256:abc123... \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
The verifier checks the following:
- The provenance signature is valid and chains to a trusted root (Sigstore for GitHub Actions builders).
- The builder identity matches a known trusted builder.
- The source repository matches the expected repository.
- The artifact digest matches the subject in the provenance.
cosign verify-attestation
For more flexible verification, cosign verify-attestation lets you verify in-toto attestations attached to container images with type filtering:
# Verify SLSA provenance attestation
cosign verify-attestation \
--type slsaprovenance \
--certificate-identity "https://github.com/myorg/myrepo/.github/workflows/release.yml@refs/tags/v1.2.3" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
ghcr.io/myorg/myimage@sha256:abc123...
# Verify with a CUE policy
cosign verify-attestation \
--type slsaprovenance \
--policy policy.cue \
ghcr.io/myorg/myimage@sha256:abc123...
# Verify with a Rego policy
cosign verify-attestation \
--type slsaprovenance \
--policy policy.rego \
ghcr.io/myorg/myimage@sha256:abc123...
gh attestation verify
For artifacts attested with GitHub’s native attestation feature, the gh CLI provides built-in verification:
# Verify a local artifact
gh attestation verify my-binary \
--owner myorg
# Verify a container image
gh attestation verify oci://ghcr.io/myorg/myimage@sha256:abc123... \
--owner myorg
# Download and inspect the attestation bundle
gh attestation download my-binary \
--owner myorg \
--output attestation.jsonl
Provenance in Admission Controllers
For production deployment gates, admission controllers can enforce provenance policies automatically. Here is an example using Sigstore’s policy-controller in a Kubernetes cluster:
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-slsa-provenance
spec:
images:
- glob: "ghcr.io/myorg/**"
authorities:
- keyless:
url: https://fulcio.sigstore.dev
identities:
- issuer: https://token.actions.githubusercontent.com
subject: "https://github.com/myorg/*"
attestations:
- name: must-have-slsa-provenance
predicateType: https://slsa.dev/provenance/v1
policy:
type: cue
data: |
predicateType: "https://slsa.dev/provenance/v1"
predicate: buildDefinition: {
buildType: =~"^https://github.com/slsa-framework/slsa-github-generator/"
}
What to Check During Verification
Regardless of which tool you use, provenance verification should confirm:
- Builder identity: Was the artifact built by a trusted build platform? Check the builder ID in the provenance against a known allowlist.
- Source repository: Does the provenance reference the expected source repository and commit? This prevents artifacts from forks or unauthorized repos from being deployed.
- Build triggers: Was the build triggered by an expected event (push to a release tag, merge to main)? This catches artifacts built from unexpected branches or events.
- Artifact digest: Does the artifact you are verifying match the subject digest in the provenance? This is the fundamental integrity check.
- Provenance freshness: Is the provenance recent? Stale provenance for old builds may not reflect current security posture.
Practical Challenges
Provenance and SLSA are powerful concepts, but real-world adoption comes with significant challenges. An honest assessment helps teams plan realistic adoption strategies.
Achieving SLSA Level 3+ Is Hard
SLSA Level 3 requires hardened, isolated build environments — and this is where most organizations hit friction. Hermetic builds mean that every dependency must be explicitly declared and fetched through controlled channels. No downloading random packages from the internet during the build. No network access to undeclared services.
For many projects, this requires fundamental changes to how builds work. Languages with rich package ecosystems (Node.js, Python, Go) often have build processes that implicitly download dependencies. Moving to a hermetic model means vendoring dependencies, using lockfiles with integrity checks, or running builds behind a dependency proxy that enforces an allowlist.
Isolated builders add operational cost. Ephemeral build environments that are destroyed after each build prevent cross-contamination but increase build times and infrastructure spend. Self-hosted runners on GitHub Actions, for example, do not provide the same isolation guarantees as GitHub-hosted runners.
Tooling Maturity and Ecosystem Gaps
The SLSA ecosystem is maturing rapidly but still has gaps:
- Trusted builders are limited. As of now, SLSA Level 3 provenance generators exist primarily for GitHub Actions. GitLab, Jenkins, CircleCI, and other platforms have less mature or community-maintained solutions.
- Verification tooling is fragmented. Different tools verify different provenance formats, and there is no universal “verify all provenance” command. Teams often need multiple tools in their verification pipeline.
- Policy languages vary. Some tools use CUE, others use Rego, and Kubernetes admission controllers each have their own policy format. Standardization is still in progress.
Provenance for Non-Container Artifacts
While container image provenance has a clear storage and distribution model (OCI registries and referrers), other artifact types face challenges:
- npm packages: npm supports provenance since May 2023, generated automatically for packages published from GitHub Actions. However, verification tooling on the consumer side is still limited.
- Python packages (PyPI): PyPI has been working on attestation support with Trusted Publishers, but the ecosystem is still early in adoption.
- Maven artifacts: Java ecosystem provenance is less mature. Projects like Sigstore for Java are emerging, but widespread adoption requires registry support.
- Generic binaries: For standalone binaries, provenance typically ships as a sidecar file (.intoto.jsonl) alongside the binary in release assets. This works but requires consumers to know where to find and how to verify it.
Balancing Strictness with Developer Velocity
Strict provenance requirements can slow down development workflows:
- Local development: Developers need to test builds locally, but local builds cannot produce SLSA Level 2+ provenance. Teams need to distinguish between “development builds” and “release builds” without creating a process so complex that developers bypass it.
- Incremental adoption: Going from zero provenance to SLSA Level 3 in one step is rarely feasible. Teams that try often abandon the effort. A phased approach — Level 1 first, then Level 2, then Level 3 for critical paths — is more sustainable.
- Build reproducibility: Provenance tells you how something was built, but it does not guarantee that the same inputs always produce the same output. Non-reproducible builds make it harder to verify provenance claims independently.
- Emergency deployments: In incident response scenarios, teams may need to deploy quickly from non-standard build paths. Provenance policies need escape hatches (with appropriate logging and audit trails) to avoid blocking critical fixes.
Conclusion
Artifact provenance closes a fundamental gap in software supply chain security. While signatures prove who approved an artifact, provenance proves how it was actually built. Together with the SLSA framework’s maturity model and in-toto’s attestation format, we now have a practical, standardized approach to build integrity.
The key takeaways for teams starting this journey:
- Start at SLSA Level 1. Generate provenance metadata for your builds, even if it is not yet signed by the build platform. This gives you auditability and establishes the practice.
- Move to Level 2 with hosted builders. Use GitHub Actions, Google Cloud Build, or another hosted platform that can sign provenance on your behalf. This is where provenance becomes meaningfully verifiable.
- Target Level 3 for critical paths. For your most sensitive artifacts — production container images, signed releases, security-critical libraries — invest in hermetic builds and isolated build environments.
- Verify provenance in your deployment pipeline. Generating provenance without verifying it is security theater. Add verification to your admission controllers, deployment scripts, or pull-based GitOps workflows.
- Adopt in-toto attestations as your metadata format. The in-toto attestation format is becoming the standard for supply chain metadata, supporting not just SLSA provenance but also SBOMs, vulnerability scans, and custom predicates.
Supply chain security is not a single tool or a single check. It is a layered approach where each control — source integrity, build integrity, provenance, verification — reinforces the others. Provenance is the connective tissue that makes the whole system auditable and verifiable. Start generating it today, and iterate upward.