Understanding SLSA build levels for Git repositories Jump to heading
SLSA — Supply-chain Levels for Software Artifacts — is often presented as a checklist to be completed, and teams respond by producing a provenance file and declaring victory. The levels are more useful read as a set of questions about where an attacker could interfere between a commit and an artefact, and how much evidence you have that they did not. Several of those questions are answered, or not, by how you run Git: who can push, whether builds run from a known commit, whether the build configuration lives in the repository. This page explains the build track’s levels in terms of Git and CI practices you can inspect today, as background for the rest of build provenance and attestations.
When to use this approach Jump to heading
- Someone has asked “what SLSA level are we?” and the honest answer is “unclear”.
- You are planning provenance work and want to know which changes buy the most assurance.
- You generate provenance already and want to understand what it does and does not prove — see generating provenance for a tagged release.
- You need to explain to an auditor how source controls and build controls relate.
Step 1 — Read the levels as threats closed Jump to heading
The build track has levels one to three. Each adds requirements on the build platform and on the provenance it produces. A useful way to remember them is by the attacker each level defeats.
The source side — whether the commit itself was reviewed and protected — is a separate concern that SLSA treats on its own track. In practice your branch protection, review requirements and commit verification gates are what make the commit named in provenance trustworthy.
Step 2 — Check Level 1: does provenance exist and name the commit? Jump to heading
Level 1 asks that each artefact has provenance describing how it was built: the build platform, the entry point, and the source — including the exact commit. You can test this without any special tooling: pick a release artefact and try to answer, from documents alone, which commit it came from.
# Does each release carry a provenance document?
gh release view v2.4.0 --json assets --jq '.assets[].name' | grep -E 'intoto|provenance'
# Does the provenance name the commit?
jq -r '.predicate.buildDefinition.resolvedDependencies[]? | select(.uri|test("git")) | .digest.gitCommit' \
provenance.intoto.json If the answer is a branch name rather than a commit hash, the provenance does not meet the intent of Level 1: a branch is a moving pointer.
Step 3 — Check Level 2: is the provenance signed by the platform? Jump to heading
Level 2 requires a hosted build platform that generates and signs the provenance itself, so a person cannot hand-edit it after the fact. “Hosted” rules out builds on a developer’s laptop. “The platform signs” rules out a build step that writes JSON and signs it with a key the build steps can also read.
# Verify a release attestation was produced by the expected workflow on the forge
gh attestation verify ./app-2.4.0.tar.gz --repo "$OWNER/$REPO" \
--signer-workflow "$OWNER/$REPO/.github/workflows/release.yml" Step 4 — Check Level 3: is the build isolated from itself? Jump to heading
Level 3 requires that builds cannot influence one another and that the signing material is inaccessible to user-defined build steps. Two common Git-adjacent practices break isolation: caches shared across branches, which let one branch’s build poison another’s, and self-hosted runners reused between jobs without cleanup.
# Are caches keyed by branch or shared across all refs?
grep -rn 'actions/cache' .github/workflows/ -A6 | grep -E 'key:|restore-keys:'
# Are self-hosted runners ephemeral (one job per runner)?
gh api "repos/$OWNER/$REPO/actions/runners" --jq '.runners[] | {name, labels: [.labels[].name]}' The cache problem is covered in depth in fixing cache poisoning between branches. Ephemeral runners are covered in autoscaling self-hosted runners.
Step 5 — Write down where you are and what moves you up Jump to heading
A short assessment is more useful than a badge. For each level, list what is in place, what is missing, and the specific change that would close the gap.
# slsa-assessment.md — kept in the repository, reviewed each quarter
Build L1: provenance attached to releases; names commit since 2026-08. MET
Build L2: provenance signed by workflow identity via forge attestation. MET
Build L3: hosted ephemeral runners; caches keyed per ref since 2026-09;
release job has no cache restore. MET (review Q1)
Source: branch protection + required review + signed commits on main. see access policy Validation checklist Jump to heading
Frequently Asked Questions Jump to heading
Does signing commits get us a SLSA level? Jump to heading
Not on the build track. Commit signing strengthens the source side — it makes the commit named in provenance attributable — but build levels are about how the artefact was produced from that commit.
Is Level 3 realistic for a small team? Jump to heading
Often, yes, on hosted CI. Hosted runners are ephemeral and isolated by default, and forge-native attestations keep signing material away from build steps. The usual remaining work is removing shared caches from release builds.
Do we need provenance for every build, or only releases? Jump to heading
Only for artefacts someone else consumes. Pull-request builds that are thrown away do not need provenance; anything deployed or published does.
Related Jump to heading
- Build Provenance & Attestations — the parent topic.
- Generating Provenance for a Tagged Release — producing the document Level 1 asks for.
- Verifying Attestations Before Deploy — making provenance actually gate something.
- Designing Branch Protection Rulesets for an Org — the source-side controls that make the named commit trustworthy.