Skip to content
DevOps AI ToolKit
Newsletter
All guides
AI for DevOps Security & Hardening By James Joyner IV · · 11 min read

Reaching SLSA Build Level 3: Ephemeral Runners and Provenance You Can't Forge

What SLSA Build L3 actually requires — runner ephemerality, build isolation, and non-falsifiable provenance — and how to assess your CI honestly instead of overclaiming a level.

  • #security
  • #ai
  • #slsa
  • #supply-chain
  • #ci-cd

Most teams that say they are “at SLSA Level 3” are at Level 2 and have not noticed. The gap is subtle and it is almost always one of two things: builds run on reused, long-lived runners that can carry state from one job to the next, or build steps generate their own provenance, which means the thing attesting to the build is controlled by the thing being attested. Both quietly cap you below L3, and neither shows up in a casual self-assessment because the pipeline produces signed provenance either way. Understanding what L3 actually demands — and being honest about where you fall short — is worth more than a confident overclaim that misleads everyone downstream.

The Three Things L3 Is Really About

SLSA Build levels describe how trustworthy a build process is, not how secure the resulting code is. Level 3, the highest build level in current SLSA, comes down to three properties:

  • Ephemeral, isolated runners. Each build runs in a fresh environment that is destroyed afterward, so one build cannot influence another through cached state, shared volumes, or a poisoned runner.
  • Build isolation from provenance. Provenance is generated by a trusted control plane that the build steps cannot tamper with. The build does its work; something the build cannot touch records what happened.
  • Non-falsifiable provenance. The provenance captures the source revision, the builder identity, and the inputs, is signed by the platform, and is bound to the artifact by digest — so it cannot be forged or swapped after the fact.

Notice what is not on that list: scanning, testing, or how clean the code is. Those matter, but they are orthogonal to SLSA build level. Conflating “we scan our images” with “we’re at L3” is the most common category error in supply-chain conversations.

Where Self-Hosted Runners Trip the Ephemerality Requirement

Ephemerality is where most platforms actually fail. A GitHub-hosted runner is fresh per job and torn down after — that property comes for free. But the moment a team moves to self-hosted runners for cost or access reasons, ephemerality becomes something you have to build, and the easy setup reuses a long-lived VM across many jobs.

A reused runner is a shared mutable environment. A malicious or compromised build can leave behind a poisoned tool, a modified cache, or an altered PATH that the next build picks up. That is precisely the cross-build contamination L3 forbids. The fix is ephemeral self-hosted runners — a fresh VM or a single-use container per job, provisioned just-in-time and destroyed after — but it is real work, and a lot of “L3” platforms skipped it.

# The shape you want: a fresh, single-use runner per job, not a pooled VM.
# (GitHub Actions ephemeral self-hosted runner registration)
./config.sh --url https://github.com/example/repo \
  --token "$RUNNER_TOKEN" \
  --ephemeral          # de-registers and the host is destroyed after one job

If your runners are pooled and reused, you are not at L3, full stop — and no amount of provenance signing changes that.

Why Build-Generated Provenance Doesn’t Count

The second common failure is letting the build write its own provenance. If a build step runs a tool that emits the provenance document and signs it with credentials the build can read, then a compromised build can simply write false provenance. The attestation is only as trustworthy as the least-trusted thing that can influence it.

L3 requires provenance to come from a control plane the build steps cannot tamper with. On GitHub Actions, the SLSA provenance generator achieves this by running provenance generation in a separate, trusted reusable workflow with its own isolated context and OIDC identity — the build job hands off the artifact digest, and a job the build cannot modify produces and signs the attestation. The signing identity belongs to the platform, not to user-controlled steps.

# Provenance generated by a trusted, separate job — not by the build steps.
jobs:
  build:
    outputs:
      digest: ${{ steps.build.outputs.digest }}
    # ... builds the artifact, emits its digest ...
  provenance:
    needs: [build]
    permissions:
      id-token: write   # OIDC identity for keyless signing
      contents: read
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v2
    with:
      digest: ${{ needs.build.outputs.digest }}

The structural point is the separation: the provenance job consumes the digest but runs in its own isolated context with its own identity, so the build job cannot forge or alter what it attests.

Assessing Yourself Honestly With AI

The temptation in any SLSA self-assessment is to grade generously. A model is useful here precisely because you can instruct it to grade against the spec and refuse to assume a control passes when the evidence does not show it:

Prompt: “Assess this CI setup against SLSA Build levels. State the current level and the single requirement that caps it there. Be strict: if I haven’t shown evidence a control is met, mark it a gap, don’t assume it passes. Setup: self-hosted runners in a Kubernetes pool, pods reused across jobs; provenance generated by a step in the build job using a PAT stored as a secret.”

Output (excerpt): “Current level: L1, capped at two requirements. (1) Runner ephemerality FAILS — pooled, reused pods allow cross-build contamination; this alone caps you below L3. (2) Provenance integrity FAILS — generation runs inside the build job with a build-readable secret, so a compromised build can forge provenance; this caps you below L3 independently. To reach L2: move provenance to a trusted, separate job with OIDC (no stored PAT). To reach L3: also make runners single-use per job. I have no evidence of two-person review on build-definition changes — treat as a gap until shown.”

That is the honest answer, and it is far more useful than a green checkmark. An overstated attestation is worse than an accurate lower one, because every downstream consumer who trusts your provenance is now trusting a claim you cannot back. Verify the model’s assessment against your real pipeline, but let it hold you to the spec.

The Roadmap, Not the Badge

Reaching L3 is an ordered set of changes, not a switch: get provenance out of the build job and onto a trusted control plane, make runners single-use, capture the source revision and inputs in the attestation, and bind everything to the artifact digest. Treat the level as a description of a process you can defend, not a badge to claim. If you want a structured way to map your current state to a level and get an ordered gap-closing plan, the SLSA L3 hosted-runner isolation prompt runs the control-by-control assessment and refuses to overstate where you are — which is exactly the discipline that makes the resulting attestation worth anything.

Free download · 368-page PDF

Download the Free 500-Prompt DevOps AI Toolkit

500 battle-tested, copy-paste AI prompts engineered by a senior systems engineer — every one with fill-in placeholders and safety/back-out notes. Drop your email and it's yours.

  • 500 prompts: Linux · Kubernetes · Terraform · OpenStack · GitLab · Docker · Monitoring · Incident Response
  • Instant PDF download — yours free, forever
  • Plus one practical AI-workflow email a week (no spam)

Single opt-in · unsubscribe anytime · no spam.