Skip to content
Provenance

SLSA Build Track Level 3 for Agent-Generated Artifacts

What SLSA Build Track Level 3 actually requires when the source-track author is an autonomous coding agent — hermetic builds, isolated builders, and signed provenance you can verify with slsa-verifier.

Advanced 12 min read Updated May 2026

SLSA Source Track answers who authored each line of code reaching production — including which autonomous agent and which model. SLSA Build Track answers a different question: how was the artifact built, by what builder, in what environment, from what inputs?

Build Track matters more, not less, when the upstream author is an LLM. Agent-generated source code is non-deterministic at authorship time; the build pipeline is the only point in the chain where determinism, hermeticity, and verifiable inputs are still achievable. Build Track 3 is where you re-establish trust after the agent has already done its non-deterministic work.

This article is the build-side companion to the source-side slsa-source-track.mdx. It covers what Build Track L3 actually requires, how to wire it for agent-generated artifacts, and how to verify it end-to-end with slsa-verifier.

SLSA Build Track Levels — Side by Side

The SLSA v1.0 specification breaks Build Track into three levels:

Level Producer Requirements Verifier Capability
L1 Provenance exists, generated by some process Verify that provenance was produced
L2 Provenance is signed by the build platform; runs on a hosted builder Verify the build platform identity
L3 Build runs on a hardened, isolated builder; provenance is unforgeable by tenants Verify the builder is L3-qualified and tenants cannot forge

L3 is the threshold where "the agent-authored code was built by something an attacker can't quietly substitute." It is also the threshold federal procurement (CNSA, FedRAMP 20x) increasingly requires for agent-touched software.

What L3 Requires When the Author Is an Agent

Build Track L3 has four hard requirements. Each one acquires extra weight when the upstream code was generated by a coding agent:

1. Hosted, isolated build environment. The builder runs on infrastructure the project does not administer. For agent code this matters because a self-hosted builder shared with the agent's runtime could let a prompt-injected agent corrupt the build. L3 forbids that topology.

2. Build inputs are fully declared. Every source repo, every dependency, every container base image must appear in the provenance. For agent runs this includes the agent's policy pack and the model fingerprint — the build provenance must be able to reach back to the agent identity certificate and prove which agent commits are baked in.

3. Provenance is unforgeable by tenants. The signing key is held by the build platform and is not exposed to the build itself. Even a build step that has been agent-tampered cannot mint a provenance for a different artifact. This is what stops "agent generates malicious build script that signs the wrong SBOM."

4. Hermetic builds. Network access during the build is restricted to declared dependencies; floating tags are pinned to digests. Agent-authored Dockerfiles love FROM node:latest — L3 says no.

A Reference L3 Build for Agent-Authored Code

The two production L3 builders today are the SLSA GitHub Generator and Google Cloud Build with the slsa.dev/v1 provenance format. The GitHub generator is the most accessible. A minimal workflow:

name: build-and-attest
on:
  push:
    tags: ['v*']

permissions:
  id-token: write
  contents: read
  packages: write

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - id: agent-context
        name: Resolve agent commits in this build
        run: |
          # Find every commit between previous tag and HEAD
          # that was authored by a registered agent identity
          PREV_TAG=$(git describe --tags --abbrev=0 HEAD^)
          git log "$PREV_TAG"..HEAD --format='%H %ae' \
            | awk '/agents\.crashoverride\.com/' \
            > agent-commits.txt
          echo "agent_commit_count=$(wc -l < agent-commits.txt)" >> "$GITHUB_OUTPUT"

      - id: build
        name: Hermetic container build
        run: |
          docker buildx build \
            --no-cache \
            --pull \
            --provenance=mode=max \
            --sbom=true \
            --tag ghcr.io/crashoverride/api-gateway:${{ github.sha }} \
            --output type=registry,push=true \
            .
          DIGEST=$(docker buildx imagetools inspect \
            ghcr.io/crashoverride/api-gateway:${{ github.sha }} \
            --format '{{ .Manifest.Digest }}')
          echo "digest=$DIGEST" >> "$GITHUB_OUTPUT"

  provenance:
    needs: build
    permissions:
      id-token: write
      packages: write
      actions: read
    uses: slsa-framework/slsa-github-generator/.github/workflows/[email protected]
    with:
      image: ghcr.io/crashoverride/api-gateway
      digest: ${{ needs.build.outputs.digest }}
      registry-username: ${{ github.actor }}
    secrets:
      registry-password: ${{ secrets.GITHUB_TOKEN }}

The split is critical: the build job produces the artifact; the provenance job — which runs on a separate, locked-down reusable workflow — generates and signs the provenance. The build job never touches the signing key. That is what unforgeable by tenants means in practice.

What the L3 Provenance Looks Like

The generated SLSA v1.0 provenance for an agent-built container:

{
  "_type": "https://in-toto.io/Statement/v1",
  "subject": [{
    "name": "ghcr.io/crashoverride/api-gateway",
    "digest": { "sha256": "def456ghi789..." }
  }],
  "predicateType": "https://slsa.dev/provenance/v1",
  "predicate": {
    "buildDefinition": {
      "buildType": "https://slsa-framework.github.io/container-generator/buildtype/v1",
      "externalParameters": {
        "source": "git+https://github.com/crashoverride/api-gateway@refs/tags/v2.14.1",
        "image": "ghcr.io/crashoverride/api-gateway"
      },
      "internalParameters": {
        "GITHUB_EVENT_NAME": "push",
        "GITHUB_REF": "refs/tags/v2.14.1",
        "GITHUB_REPOSITORY_OWNER_ID": "147823891",
        "GITHUB_RUNNER_ENVIRONMENT": "github-hosted"
      },
      "resolvedDependencies": [{
        "uri": "git+https://github.com/crashoverride/api-gateway@refs/tags/v2.14.1",
        "digest": { "gitCommit": "def456ghi789..." }
      }]
    },
    "runDetails": {
      "builder": {
        "id": "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0"
      },
      "metadata": {
        "invocationId": "https://github.com/crashoverride/api-gateway/actions/runs/9482839201/attempts/1",
        "startedOn": "2026-05-01T14:32:00Z",
        "finishedOn": "2026-05-01T14:38:42Z"
      }
    }
  }
}

Two fields earn their keep:

  • runDetails.builder.id is pinned to a tagged release of the reusable workflow. A verifier can confirm the build ran on the L3-qualified workflow, not a fork.
  • internalParameters.GITHUB_RUNNER_ENVIRONMENT proves the build ran on a GitHub-hosted runner (isolated). A self-hosted runner would show self-hosted here and fail L3 verification.

Verification with slsa-verifier

The verifier side is where Build Track L3 turns into a deployment gate:

# Install
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest

# Verify a container artifact
slsa-verifier verify-image \
  ghcr.io/crashoverride/api-gateway@sha256:def456ghi789... \
  --source-uri github.com/crashoverride/api-gateway \
  --source-tag v2.14.1 \
  --builder-id 'https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0'

A successful run prints:

PASSED: SLSA verification passed

Wire it into your deployment pipeline:

#!/usr/bin/env bash
set -euo pipefail

IMAGE="ghcr.io/crashoverride/api-gateway@sha256:${DIGEST}"
EXPECTED_BUILDER='https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v2.1.0'

if ! slsa-verifier verify-image "$IMAGE" \
    --source-uri "github.com/crashoverride/api-gateway" \
    --source-tag "$RELEASE_TAG" \
    --builder-id "$EXPECTED_BUILDER"; then
  echo "FAIL: artifact does not have a verified SLSA L3 build provenance" >&2
  exit 1
fi

# Cross-check: is the agent-commits manifest present?
PROV=$(cosign download attestation "$IMAGE" \
  | jq -r '.payload | @base64d | fromjson | .predicate')
AGENT_COUNT=$(echo "$PROV" \
  | jq -r '.buildDefinition.resolvedDependencies | length')

if [ "$AGENT_COUNT" -lt 1 ]; then
  echo "FAIL: provenance has no resolved dependencies" >&2
  exit 1
fi

kubectl set image deployment/api-gateway api-gateway="$IMAGE"

The shell gate now refuses any artifact that didn't run on the pinned L3 workflow, even if it's signed.

Composing Build Track L3 with Agent Source Provenance

Where this becomes powerful is when you join the build provenance to the agent source provenance. The build provenance lists resolvedDependencies[0].digest.gitCommit. That commit's signature, by the agent identity workflow described in our cryptographic provenance article, carries the agent's model and policy hashes. A combined verifier:

# 1. Verify the build was L3
slsa-verifier verify-image "$IMAGE" \
  --source-uri "github.com/crashoverride/api-gateway" \
  --source-tag "$RELEASE_TAG" \
  --builder-id "$EXPECTED_BUILDER" || exit 1

# 2. Pull the source commit from build provenance
COMMIT=$(slsa-verifier verify-image "$IMAGE" \
  --source-uri "github.com/crashoverride/api-gateway" \
  --source-tag "$RELEASE_TAG" \
  --builder-id "$EXPECTED_BUILDER" \
  --print-provenance \
  | jq -r '.predicate.buildDefinition.resolvedDependencies[0].digest.gitCommit')

# 3. For every agent-authored commit reachable from $COMMIT,
#    verify the agent identity signature
for HASH in $(git log "$COMMIT" --format='%H' --grep '^Agent-Authored:'); do
  cosign verify-blob \
    --certificate-identity-regexp '^agent:.*' \
    --certificate-oidc-issuer https://agents.crashoverride.com \
    --signature "$HASH.sig" \
    "$HASH.commit-msg" || {
      echo "FAIL: agent commit $HASH lacks valid agent identity signature" >&2
      exit 1
    }
done

This is the chain regulators are starting to ask for: from a running container in production back to a specific agent run, with both build and source provenance signed and verifiable.

The Hermeticity Problem with Agent-Generated Build Files

Agents love to write Dockerfiles like:

FROM node:latest
RUN apt-get update && apt-get install -y curl
RUN curl https://example.com/install.sh | bash
COPY . .
RUN npm install

Every line breaks L3 hermeticity. node:latest is a floating tag. apt-get update reaches the network unpinned. curl | bash is uncontrolled remote code. npm install resolves dependencies at build time without a lockfile guarantee.

A pre-merge gate that rejects agent-authored Dockerfiles that violate hermeticity:

# Run this in CI on every PR with agent-authored changes to Dockerfile
ERRORS=0

if grep -E '^FROM .*:latest' Dockerfile; then
  echo "Dockerfile uses floating :latest tag — pin to digest"
  ERRORS=$((ERRORS+1))
fi

if grep -E '^RUN curl .* \| (bash|sh)' Dockerfile; then
  echo "Dockerfile pipes curl to shell — fetch and verify instead"
  ERRORS=$((ERRORS+1))
fi

if grep -E '^FROM [^@]+$' Dockerfile | grep -v '@sha256:'; then
  echo "Base image not pinned to sha256 digest"
  ERRORS=$((ERRORS+1))
fi

exit "$ERRORS"

Run this in the same PR check that flags agent commits. Agents are great at writing code that works; they are mediocre at writing code that builds hermetically. The gate is what stops the difference from leaking into production.

What This Means in Practice

For a team adopting Build Track L3 on agent-authored code:

  • Move builds to a hosted, isolated runner. Stop using self-hosted GitHub Actions runners for any artifact that ships to production.
  • Adopt the SLSA reusable workflow generators. The pinned @v2.1.0 reusable workflow is the L3 boundary; do not fork it.
  • Pin every base image to a sha256 digest. Add a CI gate that fails PRs introducing floating tags.
  • Enforce slsa-verifier at the deployment gate. Unsigned or unverified artifacts must not deploy.
  • Join build provenance to source provenance. The chain from running container → build run → source commit → agent identity is what compliance auditors will ask for.

Build Track L3 is achievable today on GitHub Actions for most teams in under a sprint. The harder work is the cultural shift: treating agent-authored Dockerfiles and CI configs with the same suspicion you'd apply to a pull request from an unknown contributor.

Sources

This article is part of the Provenance knowledge series (4 articles) Browse all Provenance articles →
Related Use Case

Software Compliance — Your last compliance vendor

Don't fake the evidence. Trust it.

Explore Use Case →