17.5 Build Provenance and Attestation¶
When the Log4Shell vulnerability emerged in December 2021, organizations scrambled to answer a basic question: "Which of our deployed applications contain Log4j?" Many couldn't answer because they lacked records connecting deployed artifacts to their build inputs. Build provenance solves this by creating an unforgeable record of how software was built—what sources, what dependencies, what toolchain, and what process produced each artifact. Combined with attestations that vouch for security properties, provenance enables verification that was previously impossible.
This section explains build provenance and attestation as mechanisms for establishing trust in build outputs, covering standards like in-toto and SLSA, and practical implementation guidance.
What Is Provenance?¶
Provenance is metadata describing the origin and build process of a software artifact. It answers fundamental questions about any artifact:
- What source code was used?
- What dependencies were included?
- What build commands were executed?
- What build system was used?
- Who or what triggered the build?
- When was it built?
Provenance Components:
| Component | Description | Example |
|---|---|---|
| Subject | The artifact being described | sha256:abc123... of binary |
| Builder | System that performed the build | GitHub Actions, Jenkins |
| Source | Input source code | git+https://github.com/org/repo@v1.0.0 |
| Materials | All build inputs | Dependencies, configs, tools |
| Build config | How the build was performed | Workflow file, build script |
| Invocation | What triggered the build | Push event, manual trigger |
Provenance Example:
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "myapp-1.0.0.tar.gz",
"digest": {
"sha256": "abc123def456..."
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/Attestations/GitHubActionsWorkflow@v1",
"externalParameters": {
"workflow": {
"ref": "refs/tags/v1.0.0",
"repository": "https://github.com/myorg/myapp"
}
}
},
"runDetails": {
"builder": {
"id": "https://github.com/actions/runner"
},
"metadata": {
"invocationId": "https://github.com/myorg/myapp/actions/runs/12345"
}
}
}
}
Why Provenance Matters:
Without Provenance:
┌─────────────────────────────────────────────────────────────────┐
│ "Trust me, this binary came from that source" │
│ ┌─────────┐ ??? ┌─────────┐ │
│ │ Source │ ─────────────────►│ Binary │ │
│ └─────────┘ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
With Provenance:
┌─────────────────────────────────────────────────────────────────┐
│ Cryptographically signed record of exactly how binary was built │
│ ┌─────────┐ ┌─────────┐ │
│ │ Source │─┐ │ Binary │ │
│ └─────────┘ │ ┌───────────┐ └─────────┘ │
│ ├─►│Provenance │◄─────┘ │
│ ┌─────────┐ │ │(signed) │ │
│ │ Deps │─┤ └───────────┘ │
│ └─────────┘ │ ▲ │
│ ┌─────────┐ │ │ │
│ │ Builder │─┘ Verifiable │
│ └─────────┘ │
└─────────────────────────────────────────────────────────────────┘
Provenance extends beyond traditional audit logs—while audit logs capture what happened on a single system, provenance provides the complete cryptographically-verified history of an artifact across the entire supply chain.
In-toto Framework¶
in-toto is a framework for securing the software supply chain through cryptographically signed attestations about each step in the process.
In-toto Architecture:
| Concept | Definition |
|---|---|
| Layout | Defines expected supply chain steps and required attestations |
| Link | Cryptographically signed evidence that a step was performed |
| Functionary | Entity authorized to perform a step and sign its link |
| Step | A discrete operation in the supply chain |
| Inspection | Verification performed by the consumer |
In-toto Layout Example:
{
"_type": "layout",
"expires": "2025-01-01T00:00:00Z",
"keys": {
"abc123...": {
"keytype": "ecdsa",
"keyval": {"public": "..."}
}
},
"steps": [
{
"name": "clone",
"expected_materials": [],
"expected_products": [["CREATE", "src/*"]],
"pubkeys": ["abc123..."],
"threshold": 1
},
{
"name": "build",
"expected_materials": [["MATCH", "src/*", "WITH", "PRODUCTS", "FROM", "clone"]],
"expected_products": [["CREATE", "dist/*"]],
"pubkeys": ["def456..."],
"threshold": 1
},
{
"name": "package",
"expected_materials": [["MATCH", "dist/*", "WITH", "PRODUCTS", "FROM", "build"]],
"expected_products": [["CREATE", "*.tar.gz"]],
"pubkeys": ["ghi789..."],
"threshold": 2
}
],
"inspect": [
{
"name": "verify-signature",
"expected_materials": [["MATCH", "*.tar.gz", "WITH", "PRODUCTS", "FROM", "package"]],
"run": ["gpg", "--verify", "*.tar.gz.sig"]
}
]
}
In-toto Workflow:
┌─────────────────────────────────────────────────────────────────┐
│ IN-TOTO SUPPLY CHAIN │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Layout (signed by project owner) │
│ ├── Defines expected steps │
│ ├── Lists authorized functionaries │
│ └── Specifies material/product rules │
│ │
│ Step: Clone (Developer A signs) │
│ ├── Materials: [] │
│ └── Products: [src/main.py, src/lib.py] │
│ │
│ Step: Build (CI system signs) │
│ ├── Materials: [src/main.py, src/lib.py] │
│ └── Products: [dist/app.bin] │
│ │
│ Step: Package (Release team signs, threshold 2) │
│ ├── Materials: [dist/app.bin] │
│ └── Products: [app-1.0.0.tar.gz] │
│ │
│ Verification (Consumer runs) │
│ ├── Layout signature valid? │
│ ├── All steps have valid links? │
│ ├── Materials match previous products? │
│ └── Thresholds met? │
│ │
└─────────────────────────────────────────────────────────────────┘
In-toto Implementation:
# Step 1: Clone (developer signs link)
in-toto-run --step-name clone \
--products src/* \
--key developer-key.pem \
-- git clone https://github.com/org/repo.git
# Step 2: Build (CI signs link)
in-toto-run --step-name build \
--materials src/* \
--products dist/* \
--key ci-key.pem \
-- make build
# Step 3: Package (requires 2 signatures)
in-toto-run --step-name package \
--materials dist/* \
--products *.tar.gz \
--key release1-key.pem \
-- tar czf app-1.0.0.tar.gz dist/
in-toto-sign --link package.link --key release2-key.pem
# Verification
in-toto-verify --layout root.layout --layout-key project-key.pub
SLSA Provenance Specification¶
SLSA (Supply-chain Levels for Software Artifacts) defines provenance specifications that build on in-toto's foundation.
SLSA Provenance v1.0 Structure:
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "pkg:npm/mypackage@1.0.0",
"digest": {"sha256": "abc123..."}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://slsa.dev/github-actions-workflow/v0.1",
"externalParameters": {
"source": {
"uri": "git+https://github.com/myorg/myapp",
"digest": {"sha1": "def456..."}
}
},
"internalParameters": {
"github": {
"event_name": "push",
"ref": "refs/heads/main"
}
},
"resolvedDependencies": [
{
"uri": "pkg:npm/lodash@4.17.21",
"digest": {"sha512": "..."}
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.9.0"
},
"metadata": {
"invocationId": "https://github.com/myorg/myapp/actions/runs/12345",
"startedOn": "2024-01-15T10:30:00Z",
"finishedOn": "2024-01-15T10:35:00Z"
}
}
}
}
SLSA Provenance by Level:
| Level | Provenance Requirement |
|---|---|
| Level 1 | Provenance exists (can be unsigned) |
| Level 2 | Signed provenance from hosted build service |
| Level 3 | Hardened build platform, non-falsifiable provenance |
Key SLSA Provenance Fields:
| Field | Purpose |
|---|---|
buildType |
Identifies how to interpret the provenance |
externalParameters |
User-controlled inputs (source ref, config) |
internalParameters |
Build-system controlled parameters |
resolvedDependencies |
All dependencies with digests |
builder.id |
Identifies the trusted builder |
invocationId |
Links to specific build run |
Generating Provenance¶
Several tools and integrations generate provenance automatically.
GitHub Actions SLSA Generators:
# .github/workflows/release.yml
name: Release with SLSA Provenance
on:
push:
tags:
- 'v*'
permissions:
contents: write
id-token: write # For OIDC signing
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
artifact: ${{ steps.build.outputs.artifact }}
steps:
- uses: actions/checkout@v4
- id: build
run: |
npm ci && npm run build
ARTIFACT="dist/myapp-${{ github.ref_name }}.tar.gz"
tar czf "$ARTIFACT" dist/
echo "artifact=$ARTIFACT" >> $GITHUB_OUTPUT
echo "digest=sha256:$(sha256sum $ARTIFACT | cut -d' ' -f1)" >> $GITHUB_OUTPUT
- uses: actions/upload-artifact@v4
with:
name: build-artifact
path: ${{ steps.build.outputs.artifact }}
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.9.0
with:
base64-subjects: "${{ needs.build.outputs.digest }} ${{ needs.build.outputs.artifact }}"
upload-assets: true
npm Provenance (Native):
# npm v9.5.0+ supports native provenance
npm publish --provenance
# Provenance automatically:
# - Generated during publish
# - Signed via Sigstore
# - Attached to package in registry
# - Verifiable by consumers
Container Image Provenance:
# GitHub Actions: Container with provenance
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
steps:
- uses: actions/checkout@v4
- uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/myorg/myapp:${{ github.sha }}
provenance: true # Generates SLSA provenance
sbom: true # Also generates SBOM
Ko for Go (Built-in Provenance):
# Ko generates provenance for Go binaries in containers
KO_DOCKER_REPO=ghcr.io/myorg ko build ./cmd/app \
--provenance=medium \
--sbom=spdx
Storing and Distributing Provenance¶
Provenance must be stored durably and distributed alongside artifacts.
Storage Options:
| Option | Description | Use Case |
|---|---|---|
| OCI Registry | Stored as referrer to image | Container images |
| Sigstore Rekor | Public transparency log | Public open source |
| Private Rekor | Self-hosted transparency log | Enterprise |
| Alongside artifact | .provenance.json file |
Generic artifacts |
| Package registry | Native support (npm, PyPI) | Language packages |
OCI Registry Storage:
# Attach provenance to container image using ORAS
oras attach ghcr.io/myorg/myapp:v1.0.0 \
--artifact-type application/vnd.in-toto+json \
provenance.json
# List referrers (attestations attached to image)
oras discover ghcr.io/myorg/myapp:v1.0.0
# Fetch provenance
oras pull ghcr.io/myorg/myapp:v1.0.0 \
--artifact-type application/vnd.in-toto+json \
--output provenance.json
Sigstore Rekor (Transparency Log):
# Upload attestation to Rekor
rekor-cli upload \
--artifact myapp-1.0.0.tar.gz \
--type intoto \
--attestation provenance.json \
--public-key builder.pub
# Verify entry exists
rekor-cli verify \
--artifact myapp-1.0.0.tar.gz \
--type intoto \
--attestation provenance.json \
--public-key builder.pub
# Search for provenance
rekor-cli search --artifact myapp-1.0.0.tar.gz
Transparency Log Properties:
┌─────────────────────────────────────────────────────────────────┐
│ TRANSPARENCY LOG │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Properties: │
│ • Append-only: Entries cannot be removed or modified │
│ • Merkle tree: Cryptographic proof of inclusion │
│ • Public: Anyone can verify entries │
│ • Auditable: Third parties can monitor for anomalies │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ Merkle Tree │ │
│ │ [root] │ │
│ │ / \ │ │
│ │ [h12] [h34] │ │
│ │ / \ / \ │ │
│ │ [h1] [h2] [h3] [h4] │ │
│ │ │ │ │ │ │ │
│ │ [e1] [e2] [e3] [e4] ← Attestation entries │ │
│ └───────────────────────────────────────────────────────────┘ │
│ │
│ Inclusion proof: log[h1] + h2 + h34 → root ✓ │
│ │
└─────────────────────────────────────────────────────────────────┘
Verification at Deployment¶
Provenance is only valuable if verified before deployment.
Verification Workflow:
┌─────────────────────────────────────────────────────────────────┐
│ DEPLOYMENT VERIFICATION │
├─────────────────────────────────────────────────────────────────┤
│ │
│ 1. Fetch artifact │
│ └── Download from registry │
│ │
│ 2. Fetch provenance │
│ └── From attached referrer or separate location │
│ │
│ 3. Verify provenance signature │
│ └── Signed by trusted builder? │
│ │
│ 4. Check provenance claims │
│ ├── Builder ID in allowed list? │
│ ├── Source repository matches expected? │
│ ├── Branch/tag policy satisfied? │
│ └── Build type acceptable? │
│ │
│ 5. Verify artifact matches subject │
│ └── Artifact digest matches provenance subject? │
│ │
│ 6. Deploy or reject │
│ │
└─────────────────────────────────────────────────────────────────┘
slsa-verifier Tool:
# Verify SLSA provenance for generic artifact
slsa-verifier verify-artifact myapp-1.0.0.tar.gz \
--provenance-path provenance.intoto.jsonl \
--source-uri github.com/myorg/myapp \
--builder-id https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@refs/tags/v1.9.0
# Verify npm package provenance
slsa-verifier verify-npm-package mypackage \
--package-version 1.0.0 \
--source-uri github.com/myorg/mypackage
# Verify container image
slsa-verifier verify-image ghcr.io/myorg/myapp@sha256:abc123... \
--source-uri github.com/myorg/myapp
Kubernetes Admission Control:
# Kyverno policy: Require SLSA provenance
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-slsa-provenance
spec:
validationFailureAction: Enforce
background: false
rules:
- name: verify-slsa-provenance
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/myorg/*"
attestations:
- type: https://slsa.dev/provenance/v1
conditions:
- all:
- key: "{{ builder.id }}"
operator: Equals
value: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@*"
- key: "{{ invocation.configSource.uri }}"
operator: Equals
value: "git+https://github.com/myorg/*"
Sigstore Policy Controller:
# Sigstore policy requiring provenance
apiVersion: policy.sigstore.dev/v1beta1
kind: ClusterImagePolicy
metadata:
name: require-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/myrepo/.github/workflows/release.yml@refs/heads/main
attestations:
- name: must-have-slsa
predicateType: https://slsa.dev/provenance/v1
policy:
type: cue
data: |
builder: id: =~ "^https://github.com/slsa-framework/slsa-github-generator/"
Provenance vs. Signing¶
Provenance and signing are complementary, not alternatives.
Different Functions:
| Aspect | Signing | Provenance |
|---|---|---|
| What it proves | Artifact unchanged since signed | How artifact was created |
| Information content | Identity of signer | Full build context |
| Verification question | "Who vouches for this?" | "How was this built?" |
| Use case | Integrity verification | Supply chain traceability |
How They Work Together:
┌─────────────────────────────────────────────────────────────────┐
│ SIGNING + PROVENANCE TOGETHER │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ Artifact │ │
│ │ (myapp.tar.gz) │ │
│ └──────────────────────────┬──────────────────────────────┘ │
│ │ │
│ ┌───────────────────┼───────────────────┐ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Signature │ │ Provenance │ │ SBOM │ │
│ │ │ │ │ │ │ │
│ │ "Unchanged │ │ "Built from │ │ "Contains │ │
│ │ since built"│ │ this source │ │ these deps" │ │
│ │ │ │ by this CI" │ │ │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ └───────────────────┼───────────────────┘ │
│ ▼ │
│ All signed together │
│ (Sigstore bundle) │
│ │
└─────────────────────────────────────────────────────────────────┘
Sigstore Bundle:
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"mediaType": "application/vnd.dev.sigstore.bundle+json;version=0.2",
"verificationMaterial": {
"certificate": "...",
"tlogEntries": [{"...": "..."}]
},
"dsseEnvelope": {
"payload": "base64(provenance)",
"payloadType": "application/vnd.in-toto+json",
"signatures": [{"sig": "...", "keyid": "..."}]
}
}
Ecosystem Adoption Status¶
Provenance adoption is accelerating across ecosystems.
Ecosystem Status (as of 2024):
| Ecosystem | Native Provenance | Tool Support | Verification |
|---|---|---|---|
| npm | ✅ Yes (--provenance) |
Excellent | npm CLI, slsa-verifier |
| PyPI | ✅ Yes (Attestations) | Good | pip (planned), slsa-verifier |
| Go | ⚠️ Partial | slsa-github-generator | slsa-verifier |
| Container | ✅ Yes (Docker BuildKit) | Excellent | cosign, Kyverno |
| Maven | ⚠️ Limited | slsa-github-generator | slsa-verifier |
| GitHub Releases | ✅ Yes | slsa-github-generator | slsa-verifier |
npm Provenance Example:
# Publishing with provenance
$ npm publish --provenance
# Check package provenance on npmjs.com
# Look for "Provenance" badge on package page
# Verify locally
$ npm audit signatures
audited 100 packages in 2s
100 packages have verified attestations
Adoption Drivers:
- SLSA framework providing clear requirements
- Sigstore making signing/verification easier
- GitHub Actions native support
- Package registries adding native support
- Enterprise security requirements
Recommendations¶
For Build Engineers:
-
Start with GitHub Actions generators. The SLSA GitHub generators provide turnkey provenance generation with minimal configuration. Begin there and expand.
-
Use native provenance when available. npm, PyPI, and container registries now support native provenance. Enable it—it's often a single flag.
-
Store provenance alongside artifacts. Use OCI referrers for containers,
.provenance.jsonfiles for other artifacts. Make provenance discoverable.
For Platform Engineers:
-
Implement verification at deployment. Use admission controllers (Kyverno, Sigstore Policy Controller) to require valid provenance before deployment.
-
Define provenance policies. Specify which builders are trusted, which source repositories are allowed, and what build types are acceptable.
-
Log verification results. Create audit trail of what provenance was verified at deployment time.
For Security Architects:
-
Map provenance to SLSA levels. Use SLSA as framework for understanding what provenance guarantees you have and need.
-
Plan for the ecosystem. Different artifacts require different provenance approaches. Define strategy across container images, language packages, and binaries.
-
Combine with SBOM. Provenance tells you how something was built; SBOM tells you what's inside. Use both together for complete visibility.
Build provenance transforms supply chain security from "trust the builder" to "verify the build." When every artifact carries cryptographically signed metadata about its origin, tampering becomes detectable and accountability becomes possible. As provenance tooling matures and adoption spreads, this capability will become as fundamental as code signing—table stakes for secure software delivery.