Skip to content

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:

  1. Start with GitHub Actions generators. The SLSA GitHub generators provide turnkey provenance generation with minimal configuration. Begin there and expand.

  2. Use native provenance when available. npm, PyPI, and container registries now support native provenance. Enable it—it's often a single flag.

  3. Store provenance alongside artifacts. Use OCI referrers for containers, .provenance.json files for other artifacts. Make provenance discoverable.

For Platform Engineers:

  1. Implement verification at deployment. Use admission controllers (Kyverno, Sigstore Policy Controller) to require valid provenance before deployment.

  2. Define provenance policies. Specify which builders are trusted, which source repositories are allowed, and what build types are acceptable.

  3. Log verification results. Create audit trail of what provenance was verified at deployment time.

For Security Architects:

  1. Map provenance to SLSA levels. Use SLSA as framework for understanding what provenance guarantees you have and need.

  2. Plan for the ecosystem. Different artifacts require different provenance approaches. Define strategy across container images, language packages, and binaries.

  3. 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.