SLSA Framework: Achieving Supply Chain Integrity Levels
SLSA (Supply-chain Levels for Software Artifacts, pronounced "salsa") is a security framework from Google that provides a common vocabulary and checklist for supply chain integrity. Rather than describing a single policy, SLSA defines four progressively stricter levels — allowing organizations to incrementally improve their supply chain security posture.
Why SLSA Exists
Software supply chain attacks have increased dramatically. SolarWinds, CodeCov, and the XZ Utils backdoor all exploited the same weakness: the gap between "we trust this software" and "we have proof this software is what it claims to be."
SLSA addresses this by asking: can you prove how your software was built?
The framework was developed by Google based on internal practices (their Binary Authorization system), then open-sourced and standardized by the Open Source Security Foundation (OpenSSF).
The Four SLSA Levels
SLSA 1: Documented Build Process
Goal: Basic build provenance exists.
Requirements:
- The build process is scripted (not manual)
- Provenance is generated (but not necessarily verified)
- Provenance exists as a document that consumers can review
SLSA 1 is achievable by most teams using any CI system. The key artifact is provenance: a document stating what inputs went into the build, what build steps ran, and what outputs were produced.
// Minimal SLSA provenance (simplified)
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v0.2",
"subject": [{
"name": "myapp",
"digest": {"sha256": "abc123..."}
}],
"predicate": {
"builder": {"id": "https://github.com/actions/runner"},
"buildType": "https://github.com/slsa-framework/slsa-github-generator/...",
"invocation": {
"configSource": {
"uri": "git+https://github.com/myorg/myrepo",
"digest": {"sha1": "def456..."},
"entryPoint": ".github/workflows/release.yaml"
}
},
"buildConfig": {},
"materials": [{
"uri": "git+https://github.com/myorg/myrepo",
"digest": {"sha1": "def456..."}
}]
}
}SLSA 2: Hosted Build Service
Goal: Tamper resistance in the build process.
Requirements (everything from SLSA 1, plus):
- Build runs on a hosted CI service (not developer machines)
- Provenance is generated by the CI service itself
- Provenance is signed by the build service
SLSA 2 prevents someone from claiming a build was done by CI when it was actually done on a compromised developer laptop. The CI service's signature on the provenance provides that assurance.
Most GitHub Actions, GitLab CI, and Google Cloud Build pipelines can reach SLSA 2 with relatively minor changes.
SLSA 3: Hardened Builds
Goal: Resist insider threats and compromised CI.
Requirements (everything from SLSA 2, plus):
- Build runs in an isolated environment (each build starts fresh)
- Build inputs are fully declared in advance
- No network access after dependencies are resolved (ideally)
- Provenance cannot be falsified even by the build service operator
- Source is version controlled and the specific commit is captured in provenance
SLSA 3 is where most organizations should aim for critical software. The key distinction from SLSA 2: even if someone compromised the build service, they couldn't generate valid provenance for a tampered build.
SLSA 4: Two-Party Review + Hermetic Builds
Goal: High assurance against insider attacks.
Requirements (everything from SLSA 3, plus):
- All code changes require review by a different person (no solo merges)
- Build is fully hermetic (completely offline, all dependencies pre-fetched)
- Build is fully reproducible (same inputs always produce same outputs)
- Provenance includes all build parameters
SLSA 4 is the highest level and currently difficult to achieve in practice. Full hermeticity is hard when builds pull from the internet. It's appropriate for highly sensitive software — cryptographic libraries, security tools, CI infrastructure itself.
Achieving SLSA Levels in Practice
GitHub Actions: SLSA 3 with slsa-github-generator
The slsa-github-generator project provides reusable workflows that produce SLSA 3 provenance:
# .github/workflows/release.yaml
name: Release
on:
push:
tags: ['v*']
permissions:
actions: read
contents: write
id-token: write
packages: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v4
- name: Build and push image
id: build
uses: docker/build-push-action@v5
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }}
# SLSA provenance generation (SLSA 3)
provenance:
needs: build
permissions:
actions: read
id-token: write
packages: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@v1.10.0
with:
image: ghcr.io/${{ github.repository }}
digest: ${{ needs.build.outputs.digest }}
secrets:
registry-username: ${{ github.actor }}
registry-password: ${{ secrets.GITHUB_TOKEN }}This produces SLSA 3 provenance, signs it with Sigstore, and stores it alongside the image in the OCI registry.
Go Binary: SLSA 3
name: Release Go Binary
on:
push:
tags: ['v*']
permissions:
id-token: write
contents: write
actions: read
jobs:
build:
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build
run: |
go build -trimpath -o myapp-linux-amd64 ./cmd/myapp
go build -trimpath -o myapp-darwin-amd64 ./cmd/myapp
- name: Generate hashes
id: hash
run: |
sha256sum myapp-linux-amd64 myapp-darwin-amd64 > hashes.txt
echo "hashes=$(base64 -w0 < hashes.txt)" >> "$GITHUB_OUTPUT"
- name: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: binaries
path: |
myapp-linux-amd64
myapp-darwin-amd64
provenance:
needs: build
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v1.10.0
with:
base64-subjects: ${{ needs.build.outputs.hashes }}
upload-assets: trueManual SLSA 1 Provenance Generation
For non-GitHub environments or custom pipelines:
# Using slsa-verifier's provenance format
<span class="hljs-built_in">cat > provenance.json << <span class="hljs-string">EOF
{
"_type": "https://in-toto.io/Statement/v0.1",
"subject": [
{
"name": "myapp-v1.2.3.tar.gz",
"digest": {
"sha256": "$(sha256sum myapp-v1.2.3.tar.gz | cut -d' ' -f1)"
}
}
],
"predicateType": "https://slsa.dev/provenance/v0.2",
"predicate": {
"builder": {
"id": "https://gitlab.com/${CI_PROJECT_PATH}/-/pipelines/${CI_PIPELINE_ID}"
},
"buildType": "https://gitlab.com/gitlab-org/gitlab/-/jobs",
"invocation": {
"configSource": {
"uri": "git+https://gitlab.com/${CI_PROJECT_PATH}.git",
"digest": {"sha1": "${CI_COMMIT_SHA}"},
"entryPoint": ".gitlab-ci.yml"
}
},
"materials": [
{
"uri": "git+https://gitlab.com/${CI_PROJECT_PATH}.git",
"digest": {"sha1": "${CI_COMMIT_SHA}"}
}
]
}
}
EOFVerifying SLSA Provenance
slsa-verifier validates provenance against SLSA requirements:
# Install
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest
<span class="hljs-comment"># Verify a Go binary's provenance
slsa-verifier verify-artifact myapp-linux-amd64 \
--provenance-path myapp-linux-amd64.intoto.jsonl \
--source-uri github.com/myorg/myrepo \
--source-tag v1.2.3
<span class="hljs-comment"># Verify a container image
slsa-verifier verify-image \
ghcr.io/myorg/myapp:v1.2.3 \
--source-uri github.com/myorg/myapp \
--source-tag v1.2.3Verification checks:
- The provenance was signed by GitHub Actions (via Sigstore)
- The source repository matches the claimed repository
- The build ran on the claimed workflow
- The artifact digest matches what's in the provenance
SLSA Requirements Summary Table
| Requirement | SLSA 1 | SLSA 2 | SLSA 3 | SLSA 4 |
|---|---|---|---|---|
| Scripted build | ✓ | ✓ | ✓ | ✓ |
| Build service | ✓ | ✓ | ✓ | |
| Provenance exists | ✓ | ✓ | ✓ | ✓ |
| Provenance authenticated | ✓ | ✓ | ✓ | |
| Non-falsifiable provenance | ✓ | ✓ | ||
| Source version controlled | ✓ | ✓ | ||
| Two-party review | ✓ | |||
| Hermetic build | ✓ | |||
| Reproducible build | ✓ |
Consuming SLSA Provenance
Provenance is only useful if consumers verify it. Patterns for verification:
In Kubernetes (with Kyverno)
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-slsa-provenance
spec:
validationFailureAction: enforce
rules:
- name: verify-provenance
match:
any:
- resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["ghcr.io/myorg/*"]
attestations:
- predicateType: "https://slsa.dev/provenance/v0.2"
attestors:
- entries:
- keyless:
subject: "https://github.com/myorg/myapp/.github/workflows/release.yaml@refs/tags/v*"
issuer: "https://token.actions.githubusercontent.com"
conditions:
- all:
- key: "{{ predicate.builder.id }}"
operator: Equals
value: "https://github.com/slsa-framework/slsa-github-generator/.github/workflows/generator_container_slsa3.yml@refs/tags/v1.10.0"In a Verification Script
#!/bin/bash
<span class="hljs-comment"># verify-artifact.sh - verify SLSA provenance before deployment
ARTIFACT=<span class="hljs-variable">$1
PROVENANCE=<span class="hljs-variable">$2
SOURCE_URI=<span class="hljs-variable">$3
EXPECTED_TAG=<span class="hljs-variable">$4
slsa-verifier verify-artifact \
<span class="hljs-string">"$ARTIFACT" \
--provenance-path <span class="hljs-string">"$PROVENANCE" \
--source-uri <span class="hljs-string">"$SOURCE_URI" \
--source-tag <span class="hljs-string">"$EXPECTED_TAG"
<span class="hljs-keyword">if [ $? -eq 0 ]; <span class="hljs-keyword">then
<span class="hljs-built_in">echo <span class="hljs-string">"✓ Provenance verified - artifact is authentic"
<span class="hljs-built_in">exit 0
<span class="hljs-keyword">else
<span class="hljs-built_in">echo <span class="hljs-string">"✗ Provenance verification FAILED"
<span class="hljs-built_in">exit 1
<span class="hljs-keyword">fiSLSA in the Dependency Graph
SLSA becomes most powerful when applied transitively. If your build requires dependencies, those dependencies should also have SLSA provenance.
The long-term vision: every dependency you pull has verifiable provenance, so you can answer "was this package built from the source it claims, by the CI service it claims?"
Tools like deps.dev from Google are building provenance tracking for popular open source packages.
What SLSA Doesn't Cover
SLSA focuses on the build process, not:
- Code quality: SLSA doesn't audit for vulnerabilities or bugs
- Runtime behavior: Provenance doesn't prove the code does what it should
- Key compromise: If the signing key is stolen, provenance can be faked
- Source trust: SLSA trusts that the source repository is controlled by the right people
These gaps are addressed by complementary controls: code review, vulnerability scanning (Grype/Trivy), and access control.
Practical Starting Point
Most organizations should target SLSA 2 as a baseline and SLSA 3 for production software:
- Start with GitHub Actions: slsa-github-generator makes SLSA 3 achievable in an afternoon
- Automate verification: Add slsa-verifier checks to your deployment pipeline
- Document your current level: Audit each repository and note its SLSA level
- Require SLSA 2+ for third-party dependencies you embed or vendor
SLSA is not about achieving level 4 immediately — it's about having a clear vocabulary for discussing and improving supply chain security incrementally. Level 1 today is better than level 0.