Introduction to the SLSA Framework
SLSA (Supply-chain Levels for Software Artifacts, pronounced "salsa") is a security framework developed by Google and now part of the Open Source Security Foundation (OpenSSF). It provides a graduated set of requirements and recommendations designed to ensure the integrity of software artifacts throughout the software supply chain. In an era where supply chain attacks have become increasingly common, SLSA offers developers a structured path to harden their build and release pipelines.
At its core, SLSA answers a simple but critical question: How much can we trust the artifacts we produce and consume? The framework defines four levels of assurance, from Level 1 (basic provenance) to Level 4 (hermetic, reproducible builds with two-party review). Each level builds on the previous one, adding stricter controls and verifiable guarantees.
Why SLSA Matters
Modern software development relies heavily on third-party dependencies, CI/CD pipelines, and automated build systems. Each of these touchpoints represents a potential attack surface. High-profile incidents like the SolarWinds breach and the Codecov supply chain attack demonstrated that attackers increasingly target the build and distribution process rather than the application code itself.
SLSA matters because it provides:
- Provenance attestation — cryptographic proof of how, where, and from what an artifact was built.
- Tamper resistance — controls that make it difficult for attackers to modify build processes undetected.
- Verifiable trust — consumers can independently verify the integrity of dependencies they use.
- Graduated adoption — teams can incrementally improve their security posture without a massive upfront investment.
Understanding the SLSA Levels
SLSA Level 1: Basic Provenance
Level 1 requires that the build process automatically generates provenance documentation. This means recording what source was built, how it was built, and what was produced. There are no strict security controls on the build platform itself — the goal is simply to have a machine-readable record of the build.
SLSA Level 2: Hosted Build Service
Level 2 adds requirements around the build platform. Builds must run on a managed service (like GitHub Actions, GitLab CI, or Google Cloud Build) that generates authenticated provenance. The build service must isolate builds from one another and protect provenance from tampering.
SLSA Level 3: Hardened Build Platform
Level 3 significantly raises the bar. The build platform must have strong isolation between builds, provenance must be non-forgeable, and the source must be identified with strong integrity guarantees. This level is designed to protect against sophisticated attackers who may have some access to the build infrastructure.
SLSA Level 4: Hermetic and Reproducible
Level 4 requires hermetic builds (no network access during the build), reproducibility (the same inputs produce identical outputs), and two-party review of changes. This is the gold standard and is typically only achievable by large organizations with dedicated platform engineering teams.
Generating Provenance with GitHub Actions
The most practical starting point for most teams is implementing SLSA Level 1 or Level 2 using GitHub Actions. GitHub provides native support for generating build provenance through its attest-build-provenance action. Below is a complete workflow that builds a container image and generates SLSA provenance.
name: Build with SLSA Provenance
on:
push:
branches: [main]
tags: ['v*']
permissions:
contents: read
packages: write
id-token: write
attestations: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- name: Checkout source
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ghcr.io/${{ github.repository }}
tags: |
type=semver,pattern={{version}}
type=sha,prefix=sha-
- name: Build and push image
id: build
uses: docker/build-push-action@v6
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
provenance: false
sbom: true
- name: Generate SLSA provenance attestation
uses: actions/attest-build-provenance@v1
with:
subject-name: ghcr.io/${{ github.repository }}
subject-digest: ${{ steps.build.outputs.digest }}
push-to-registry: true
This workflow demonstrates several key concepts. The id-token: write and attestations: write permissions are required for GitHub's OIDC-based signing. The attest-build-provenance action generates an in-toto statement that cryptographically binds the built artifact to its source, build parameters, and environment.
Generating Provenance for Binary Artifacts
For projects that produce binary releases rather than container images, you can use the SLSA GitHub Generator. The following example builds a Go binary and generates provenance for the resulting artifact.
name: Release Binary with SLSA Provenance
on:
push:
tags: ['v*']
permissions:
contents: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
outputs:
hashes: ${{ steps.hash.outputs.hashes }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: '1.22'
- name: Build binary
run: |
CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o myapp ./cmd/myapp
- name: Generate subject hash
id: hash
run: |
echo "hashes=$(sha256sum myapp | base64 -w0)" >> "$GITHUB_OUTPUT"
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: myapp-binary
path: myapp
provenance:
needs: [build]
permissions:
actions: read
id-token: write
contents: write
uses: slsa-framework/slsa-github-generator/.github/workflows/generator-binary-slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.build.outputs.hashes }}"
upload-assets: true
release:
needs: [build, provenance]
runs-on: ubuntu-latest
steps:
- name: Download binary
uses: actions/download-artifact@v4
with:
name: myapp-binary
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
myapp
*.intoto.jsonl
The slsa-github-generator reusable workflow runs in a hardened, isolated environment that is separate from your own repository's workflow. This separation is a key SLSA Level 3 requirement — the provenance generation cannot be tampered with even if an attacker compromises your repository's workflow files.
Verifying Provenance
Generating provenance is only half the equation. Consumers of your artifacts need a way to verify that provenance. GitHub provides the gh CLI with attestation verification capabilities, and the cosign tool from Sigstore offers cross-platform verification.
# Verify a container image's provenance using GitHub CLI
gh attestation verify \
--owner my-org \
ghcr.io/my-org/myapp@sha256:abc123...
# Verify a binary artifact using cosign
cosign verify-attestation \
--certificate-identity https://github.com/my-org/my-repo/.github/workflows/release.yml@refs/tags/v1.0.0 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
myapp
# Verify and inspect the provenance content
gh attestation verify \
--owner my-org \
--bundle provenance.intoto.jsonl \
myapp
For automated verification in CI pipelines, you can script the verification step and fail the build if provenance is missing or invalid. This is especially important when pulling dependencies from third-party registries.
Implementing Source Integrity Controls
SLSA Level 2 and above require strong source integrity guarantees. This means ensuring that the code being built actually came from the expected source and was not tampered with between commit and build. GitHub's branch protection rules and tag protection rules are the primary mechanisms for achieving this.
# Example: Enforcing branch protection via GitHub API
# This script requires the gh CLI and jq
#!/bin/bash
set -euo pipefail
REPO="my-org/my-repo"
BRANCH="main"
gh api \
--method PUT \
"repos/$REPO/branches/$BRANCH/protection" \
--field "required_status_checks[strict]=true" \
--field "required_status_checks[contexts][]=ci/build" \
--field "required_status_checks[contexts][]=ci/test" \
--field "enforce_admins=true" \
--field "required_pull_request_reviews[required_approving_review_count]=2" \
--field "required_pull_request_reviews[dismiss_stale_reviews]=true" \
--field "required_pull_request_reviews[require_code_owner_reviews]=true" \
--field "restrictions=" \
--field "allow_force_pushes=false" \
--field "allow_deletions=false"
Key source integrity controls include requiring two-party review for all changes, preventing force pushes, requiring status checks to pass before merge, and enforcing code owner reviews for sensitive paths. These controls map directly to SLSA's source requirements.
Working with In-Toto Attestations
SLSA provenance is encoded as in-toto statements, which are JSON documents following a specific schema. Understanding this schema helps you build custom verification logic and integrate SLSA into existing security tooling.
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [
{
"name": "ghcr.io/my-org/myapp",
"digest": {
"sha256": "a1b2c3d4e5f6..."
}
}
],
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/actions/runner-github-hosted",
"externalParameters": {
"workflow": "release.yml",
"ref": "refs/tags/v1.0.0"
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "123456789"
}
},
"resolvedDependencies": [
{
"uri": "git+https://github.com/my-org/my-repo@refs/tags/v1.0.0",
"digest": {
"gitCommit": "abcdef1234567890..."
}
}
]
},
"runDetails": {
"builder": {
"id": "https://github.com/actions/runner/github-hosted"
},
"metadata": {
"invocationId": "run-123456",
"startedOn": "2024-01-15T10:00:00Z",
"finishedOn": "2024-01-15T10:05:00Z"
}
}
}
}
The subject field identifies the artifact being attested. The predicate contains the SLSA-specific provenance data, including the build definition (what was built and how), the resolved dependencies (what source was used), and run details (where and when the build executed). Verifiers check that the subject digest matches the artifact they downloaded and that the build type and source match their policy.
Policy Enforcement with OPA and Cosign
For organizations managing many repositories, manual verification is not scalable. You can automate policy enforcement using Open Policy Agent (OPA) combined with Cosign. This allows you to define rules about what provenance is acceptable and automatically reject artifacts that do not comply.
# policy.rego - OPA policy for SLSA provenance verification
package slsa
import future.keywords.in
default allow := false
allow if {
count(subjects) > 0
valid_build_type
valid_source
valid_builder
}
subjects := [s | some s in input.subject]
valid_build_type if {
input.predicate.buildDefinition.buildType in allowed_build_types
}
allowed_build_types := {
"https://github.com/actions/runner-github-hosted",
"https://github.com/actions/runner/github-hosted"
}
valid_source if {
some dep in input.predicate.buildDefinition.resolvedDependencies
startswith(dep.uri, "git+https://github.com/my-org/")
}
valid_builder if {
startswith(input.predicate.runDetails.builder.id, "https://github.com/actions/")
}
# Verify artifact against policy using cosign and opa
cosign verify-attestation \
--certificate-identity-regexp "https://github.com/my-org/.+/.github/workflows/.+" \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--type slsaprovenance \
--policy policy.rego \
ghcr.io/my-org/myapp:v1.0.0
This approach lets you enforce organizational policy at deployment time. For example, you can require that all production deployments use artifacts built from the main branch, by trusted builders, with provenance signed by GitHub's OIDC infrastructure.
Best Practices for SLSA Adoption
Start with Level 1 and Iterate
Do not attempt to jump straight to SLSA Level 3 or 4. Begin by ensuring your builds automatically generate provenance (Level 1), then work on moving to a hosted build service with authenticated provenance (Level 2). Each level requires meaningful investment in tooling and process, and the security return diminishes at higher levels for most projects.
Pin Your Dependencies
SLSA provenance is only useful if you know what went into the build. Use lockfiles for all dependency managers, pin container base images by digest (not tag), and regularly audit your dependency graph. Tools like Dependabot and Renovate can help keep pins up to date while maintaining reproducibility.
# Pin base image by digest, not just tag
FROM ubuntu@sha256:72297848456d5d37d1262630108ab308d3e9ec7ed1c3286a32fe09856619a782
# Use exact versions in package.json
{
"dependencies": {
"express": "4.19.2",
"lodash": "4.17.21"
}
}
# Pin Go module versions in go.mod
module myapp
go 1.22
require (
github.com/gorilla/mux v1.8.1
github.com/lib/pq v1.10.9
)
Separate Build and Release Workflows
Keep your build workflow and your release workflow as separate jobs with different permission scopes. The build job should only have permissions to read source and write artifacts. The release job should have permissions to publish artifacts and create releases. This separation limits the blast radius if either workflow is compromised.
Verify Before You Trust
Make provenance verification a mandatory step in your deployment pipeline. An artifact without valid, verifiable provenance should never reach production. Integrate verification into your Kubernetes admission controllers, your Terraform apply steps, or your deployment scripts.
# Example: Kubernetes admission webhook verification
# Using Kyverno policy to require SLSA provenance
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-slsa-provenance
spec:
validationFailureAction: Enforce
rules:
- name: verify-provenance
match:
resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "ghcr.io/my-org/*"
attestations:
- type: https://slsa.dev/provenance/v1
attestors:
- entries:
- keyless:
subject: "https://github.com/my-org/*"
issuer: "https://token.actions.githubusercontent.com"
Document Your SLSA Posture
Maintain a document in your repository that describes your current SLSA level, the controls you have in place, and your roadmap for reaching higher levels. This helps security teams understand your posture and helps new team members understand the build process. Consider using a SECURITY.md or a dedicated SLSA.md file.
Common Pitfalls and How to Avoid Them
One common mistake is generating provenance but never verifying it. Provenance that is never checked provides no security value — it is just metadata. Always pair provenance generation with automated verification in your consumption pipelines.
Another pitfall is using pull_request_target in GitHub Actions workflows that handle provenance. This event runs with the base branch's permissions and can be exploited by malicious pull requests. Use the standard pull_request event for workflows that build from external contributions, and reserve provenance generation for workflows triggered by merges to protected branches.
Finally, avoid storing signing keys in repository secrets when using OIDC-based keyless signing. GitHub's OIDC integration with Sigstore eliminates the need to manage long-lived signing keys, which are a significant security risk if compromised.
Conclusion
The SLSA framework provides a practical, graduated approach to securing the software supply chain. By starting with basic provenance generation and progressively adding controls around source integrity, build isolation, and verification, development teams can meaningfully reduce the risk of supply chain attacks. The key to successful adoption is treating SLSA as an ongoing practice rather than a one-time compliance exercise — generate provenance for every build, verify it at every deployment, and continuously improve your build platform's security posture. With the tools and patterns covered in this guide, you have everything you need to begin your SLSA journey and build a more trustworthy software delivery pipeline.