Software supply chain attacks—SolarWinds, the XZ Utils backdoor, the Codecov bash uploader compromise—all share a common thread: nobody could prove where the final artifact came from or whether it had been tampered with after the last trusted step. If you want to implement SLSA supply chain security across your build pipeline, good intentions and code review aren't enough. You need verifiable provenance, tamper-resistant build systems, and policies that reject anything that can't prove its lineage.
This guide walks through a practical, sequential path to implementing SLSA (Supply-chain Levels for Software Artifacts) in a real CI/CD pipeline—from mapping your current risk to generating cryptographically signed provenance and enforcing verification before deploy. Think of it as a hands-on SLSA framework tutorial rather than a policy overview: by the end you'll have a working SLSA Level 2-3 pipeline, know how to generate and validate provenance attestations, and understand which attack vectors each SLSA level actually closes.
Step 1: Map Your Current Build Pipeline and Risk Surface
Before touching tooling, inventory every step that turns source code into a shipped artifact: source control, dependency resolution, build compute, packaging, signing, registry push, and deployment. For each hop, ask two questions: who can modify this step, and is there any record proving what actually ran?
Most teams discover the same gaps:
- Build scripts run on developer laptops or shared, mutable CI runners.
- Dependencies are pulled unpinned (
npm install express, not a locked hash). - Artifacts are pushed to a registry with no attestation of how they were built.
- Nothing checks provenance before deployment accepts an image or package.
Document this as a simple diagram or table. It becomes your baseline for measuring SLSA maturity and the artifact you'll reference when prioritizing which pipelines to harden first (start with anything internet-facing or shared across teams).
Step 2: Understand the SLSA Levels Before You Build Anything
It's worth pausing here because so many teams jump straight to tooling without understanding what they're actually targeting. With the software supply chain levels explained plainly, SLSA defines a track (Build) with four levels of increasing guarantee:
- Level 0 — No guarantees. Ad hoc builds, no provenance.
- Level 1 — The build process is documented and produces provenance, but it isn't verified or tamper-resistant. A malicious insider could still forge it.
- Level 2 — Builds run on a hosted, shared build service, and provenance is generated and signed by that service (not by the build script itself), making forgery harder.
- Level 3 — The build platform is hardened against tampering even by the build definition itself—isolated, ephemeral, non-falsifiable provenance, no way for a compromised build step to alter the record of what happened.
Most organizations should target Level 2 as a near-term goal and Level 3 for critical release pipelines (anything producing artifacts that customers install or run in production). Don't try to hit Level 3 everywhere on day one—it's expensive and the marginal security gain for low-risk internal tools is small.
Step 3: Implement SLSA Supply Chain Security with a Compliant Build Platform
This is the step that actually changes your risk posture. To implement SLSA supply chain security in a way that meets Level 2+, move builds off developer machines and mutable self-hosted runners onto a build platform that can generate provenance itself—not one where the build script generates its own attestation (that's still forgeable).
For GitHub-hosted projects, the reference implementation is the SLSA GitHub Actions generator. A typical workflow for a Go binary looks like:
name: release
on:
push:
tags:
- "v*"
jobs:
build:
permissions:
id-token: write
contents: read
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
with:
base64-subjects: "${{ needs.build.outputs.digests }}"
For container images, use the container generator combined with cosign for signing:
cosign sign --key cosign.key \
registry.example.com/myapp@sha256:<digest>
The key architectural requirement: the workflow that builds your artifact and the workflow that generates provenance must be separated, with the provenance-generating step running in an isolated, reusable workflow you don't control the internals of. That separation is what makes the attestation trustworthy instead of self-reported.
Step 4: Generate SLSA Provenance for Every Build
SLSA provenance generation is the mechanism that turns "trust us" into "verify this." A provenance attestation is a signed in-toto statement recording the builder identity, source repository and commit, build parameters, and materials (dependencies) consumed.
A minimal provenance predicate looks like this:
{
"predicateType": "https://slsa.dev/provenance/v1",
"predicate": {
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"ref": "refs/tags/v1.4.2",
"repository": "https://github.com/org/myapp"
}
},
"resolvedDependencies": [
{ "uri": "git+https://github.com/org/myapp@refs/tags/v1.4.2" }
]
},
"runDetails": {
"builder": { "id": "https://github.com/actions/runner" },
"metadata": { "invocationId": "1234567890" }
}
}
}
Don't hand-roll this. Use slsa-github-generator, GoReleaser's built-in SLSA support, or your build platform's native attestation feature (GitLab, Google Cloud Build, and Buildkite all have equivalents). Hand-written provenance is easy to get subtly wrong and defeats the purpose.
Step 5: Sign and Publish Attestations Alongside Artifacts
Provenance is only useful if it's cryptographically bound to the artifact and discoverable by consumers. Sign attestations with cosign (backed by Sigstore/Fulcio for keyless signing, or your own KMS key) and push them to the same registry as the artifact:
cosign attest --predicate provenance.json \
--type slsaprovenance \
--key cosign.key \
registry.example.com/myapp@sha256:<digest>
For language package ecosystems (npm, PyPI, Maven), check whether the registry supports native attestation storage—npm provenance via npm publish --provenance is a good example that requires zero extra infrastructure if you're already building on GitHub Actions with OIDC.
Step 6: Enforce Verification Before Deployment
Generating provenance that nobody checks is security theater. Add a verification gate in your deployment pipeline or admission controller:
slsa-verifier verify-artifact myapp-linux-amd64 \
--provenance-path provenance.intoto.jsonl \
--source-uri github.com/org/myapp \
--source-tag v1.4.2
In Kubernetes, pair this with a policy engine (Kyverno or OPA/Gatekeeper) configured to reject any image pull that lacks a valid, matching attestation from your trusted signing identity. This is the control that actually stops a compromised or substituted artifact from reaching production, regardless of how it got into the registry.
Step 7: Automate, Monitor, and Expand Coverage
Once one pipeline is verified end to end, template it. Extract the workflow into a reusable module so every service onboards SLSA by referencing a shared config, not copy-pasting YAML. Track coverage as a metric ("% of production services with Level 2+ provenance") and review it in the same cadence as other security KPIs. Revisit Step 1's inventory quarterly—new services and forked pipelines drift out of compliance quietly.
Troubleshooting and Verification Checklist
slsa-verifierreports "no matching provenance found" — Usually means the source URI or tag in your verification command doesn't exactly match what's recorded in the attestation. Confirm the git ref used at build time matches what you're verifying against.- Provenance exists but signature verification fails — Check that you're verifying against the correct Sigstore/Fulcio root or KMS public key, and that the certificate hasn't expired (keyless signing certs are short-lived by design).
- Builds pass but attestation step is skipped silently — Add a required status check in branch protection rules so releases can't merge or tag without the provenance job completing successfully.
- Verify manually before trusting automation: run
cosign verify-attestationand inspect the decoded predicate to confirm builder identity, source repo, and commit hash all match expectations—don't assume a green checkmark means the content is correct. - Dependencies still unpinned — SLSA provenance for your own build is undermined if
resolvedDependenciesincludes unpinned or mutable references. Lock dependency versions and hashes before generating provenance, not after.
How Safeguard Helps
Rolling out SLSA manually across dozens of repositories and pipelines is where most teams stall—not because the concepts are hard, but because tracking provenance coverage, signing key hygiene, and verification enforcement across a real organization is an ongoing operational burden, not a one-time project.
Safeguard gives security and platform teams a single place to see SLSA maturity across every repository and pipeline: which services have provenance, which are still Level 0, and where attestations exist but aren't being enforced at deploy time. Instead of building custom dashboards on top of slsa-verifier output, Safeguard continuously scans your build systems, validates attestations against trusted signing identities, and flags drift—like a pipeline that quietly lost its provenance step after a workflow refactor. Combined with dependency and SBOM visibility, Safeguard turns SLSA from a checklist exercise into a continuously verified guarantee, so you can prove supply chain integrity to auditors, customers, and your own engineers without maintaining the plumbing by hand.