← Back to DevBytes

SLSA Supply Chain Levels: Complete Implementation Guide

Introduction to SLSA: Securing the Software Supply Chain

SLSA (Supply-chain Levels for Software Artifacts), pronounced "salsa," is a security framework developed by Google and the Open Source Security Foundation (OpenSSF) to help organizations secure their software supply chains. In an era where attackers increasingly target the build and distribution processes rather than application code itself, SLSA provides a graduated set of requirements that harden every step from source to release.

This tutorial walks you through the four SLSA levels, explains what each level demands, and shows you how to implement them in a real-world CI/CD pipeline using GitHub Actions, cosign, and SLSA provenance generators.

What Is SLSA and Why It Matters

SLSA defines a ladder of four levels (1 through 4) that measure how trustworthy a software artifact is. Each level builds on the previous one, adding stricter controls around source integrity, build platform trust, and provenance availability. The framework addresses threats such as tampering with source code, compromising build systems, and substituting malicious artifacts for legitimate ones.

The SolarWinds attack in 2020 and the Codecov breach in 2021 demonstrated that even well-funded engineering teams can fall victim to supply chain compromises. SLSA exists to make those attacks significantly harder by ensuring that every artifact can be traced back to a trusted source through verifiable provenance.

The Four SLSA Levels at a Glance

Understanding Provenance

Provenance is the cryptographic metadata that describes how an artifact was produced. At its core, provenance answers three questions: What was built? Where was it built? How was it built? SLSA provenance is typically expressed as an in-toto statement, a JSON document wrapped in a DSSE (Dead Simple Signing Envelope) payload.

Here is a simplified example of a SLSA provenance statement:

{
  "_type": "https://in-toto.io/Statement/v0.1",
  "subject": [
    {
      "name": "myapp-1.0.0.tar.gz",
      "digest": {
        "sha256": "a1b2c3d4e5f6..."
      }
    }
  ],
  "predicateType": "https://slsa.dev/provenance/v0.2",
  "predicate": {
    "builder": {
      "id": "https://github.com/actions/runner/github-hosted"
    },
    "buildType": "https://github.com/slsa-framework/slsa-github-generator/go@v1",
    "invocation": {
      "configSource": {
        "uri": "git+https://github.com/myorg/myapp",
        "digest": { "sha1": "abc123def456" },
        "entryPoint": ".github/workflows/release.yml"
      }
    },
    "metadata": {
      "buildStartedOn": "2024-01-15T10:00:00Z",
      "buildFinishedOn": "2024-01-15T10:02:30Z",
      "completeness": {
        "parameters": true,
        "environment": true,
        "materials": true
      },
      "reproducible": false
    },
    "materials": [
      {
        "uri": "git+https://github.com/myorg/myapp",
        "digest": { "sha1": "abc123def456" }
      }
    ]
  }
}

The subject field identifies the artifact being attested, while the predicate describes the build. Consumers verify provenance by checking the signature, confirming the builder identity, and ensuring the materials match expected sources.

Implementing SLSA Level 1

SLSA Level 1 requires that the build process is fully scripted (no manual steps) and that provenance is generated. This is the baseline that any automated CI/CD pipeline can achieve. The provenance does not need to be signed at this level, but it must exist and be available to consumers.

The easiest way to reach Level 1 is to use the SLSA GitHub Generator actions, which automatically produce provenance for your builds. Below is a GitHub Actions workflow that builds a Go binary and generates SLSA Level 1 provenance:

name: release
on:
  push:
    tags:
      - "v*"

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
    steps:
      - name: Checkout source
        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
          path: myapp

  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      contents: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true

This workflow produces a signed provenance file attached to your GitHub release. Even though the generator action targets Level 3 capabilities, the provenance itself satisfies Level 1 requirements when used with a standard GitHub-hosted runner.

Implementing SLSA Level 2

SLSA Level 2 adds two critical requirements: the build must run on a managed build service (not a self-hosted machine you control), and provenance must be authenticated. GitHub Actions, GitLab CI, and Google Cloud Build all qualify as managed build services. Authentication means the provenance is signed by the build platform itself, not by an individual developer.

For GitHub Actions, the SLSA generator uses OIDC tokens to authenticate provenance. The workflow above already satisfies Level 2 because it runs on GitHub-hosted runners and the generator signs provenance using GitHub's OIDC infrastructure. To verify this provenance downstream, consumers use the slsa-verifier tool:

# Install slsa-verifier
go install github.com/slsa-framework/slsa-verifier/v2/cli/slsa-verifier@latest

# Verify a binary against its provenance
slsa-verifier verify-artifact myapp \
  --provenance-path myapp.intoto.jsonl \
  --source-uri github.com/myorg/myapp \
  --source-tag v1.0.0

# Verify a container image
slsa-verifier verify-image registry.example.com/myapp:1.0.0 \
  --provenance-path myapp.intoto.jsonl \
  --source-uri github.com/myorg/myapp

If verification passes, the consumer knows the artifact was built from the specified source repository at the specified tag, on GitHub's managed infrastructure. This closes the gap between "I downloaded a binary" and "I trust this binary."

Implementing SLSA Level 3

SLSA Level 3 is where supply chain security becomes rigorous. The build platform must be hardened, builds must be isolated from one another, and provenance must be non-falsifiable. Non-falsifiable means the builder cannot generate provenance for a build it did not actually perform. GitHub Actions achieves this through ephemeral runners and OIDC-based signing, but you must also ensure your workflow follows secure practices.

Key requirements for Level 3 include:

Here is a hardened workflow that enforces Level 3 controls, including branch protection requirements and strict permission scoping:

name: secure-release
on:
  push:
    tags:
      - "v*"

permissions:
  contents: read

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
    outputs:
      hashes: ${{ steps.hash.outputs.hashes }}
    steps:
      - name: Checkout source (pinned to commit SHA)
        uses: actions/checkout@v4
        with:
          fetch-depth: 1

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version-file: go.mod
          cache: true

      - name: Run tests
        run: go test -race -coverprofile=coverage.out ./...

      - name: Build reproducible binary
        run: |
          CGO_ENABLED=0 go build \
            -trimpath \
            -ldflags="-s -w -X main.version=${GITHUB_REF_NAME}" \
            -buildvcs=false \
            -o myapp ./cmd/myapp

      - name: Generate 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
          retention-days: 7

  provenance:
    needs: [build]
    permissions:
      actions: read
      id-token: write
      contents: write
    uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
    with:
      base64-subjects: "${{ needs.build.outputs.hashes }}"
      upload-assets: true
      upload-tag-name: "${{ github.ref_name }}"

  release:
    needs: [build, provenance]
    runs-on: ubuntu-latest
    permissions:
      contents: write
    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
            myapp.intoto.jsonl
          generate_release_notes: true

To complete Level 3 compliance, configure branch protection rules on your repository requiring pull request reviews, status checks, and linear history. These settings ensure that no single developer can push code directly to the release branch without review.

Implementing SLSA Level 4

SLSA Level 4 is the most demanding tier and is rarely fully achieved today. It requires hermetic builds (no network access during the build except to declared dependencies), reproducibility (two builds from the same source produce bit-identical artifacts), and two-party review of the build platform configuration itself. Most organizations target Level 3 and treat Level 4 as an aspirational goal.

To approach Level 4, you need a build platform that supports hermetic execution. Google's internal build system and Bazel with remote build execution are examples. Here is a simplified Bazel configuration that produces reproducible, hermetic builds:

# .bazelrc
build --experimental_repository_cache_hardlinks
build --sandbox_writable_path=/tmp
build --spawn_strategy=sandboxed
build --strategy=Genrule=sandboxed
build --stamp=false
build --workspace_status_command=./tools/stable_status.sh

# Force reproducible builds
build --copt=-ffile-prefix-map=${PWD}=.
build --host_copt=-ffile-prefix-map=${PWD}=.

The stable_status.sh script provides deterministic build metadata:

#!/usr/bin/env bash
# tools/stable_status.sh
echo "BUILD_SCM_REVISION $(git rev-parse HEAD 2>/dev/null || echo unknown)"
echo "BUILD_SCM_TAG ${GITHUB_REF_NAME:-local}"

Even with Bazel, achieving true Level 4 requires a build platform that guarantees isolation and provenance non-falsifiability at the infrastructure level, which most cloud CI systems do not yet provide.

Verifying Provenance in CI/CD

Generating provenance is only half the equation. Consumers must verify it before trusting an artifact. Add a verification step to any pipeline that pulls third-party dependencies. The following GitHub Actions job verifies a downloaded binary against its SLSA provenance before deploying:

name: deploy
on:
  release:
    types: [published]

jobs:
  verify-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Download release assets
        uses: robinraju/release-downloader@v1.10
        with:
          repository: myorg/myapp
          tag: ${{ github.event.release.tag_name }}
          fileName: "*"

      - name: Install slsa-verifier
        run: |
          curl -sLo slsa-verifier \
            https://github.com/slsa-framework/slsa-verifier/releases/download/v2.5.1/slsa-verifier-linux-amd64
          chmod +x slsa-verifier

      - name: Verify provenance
        run: |
          ./slsa-verifier verify-artifact myapp \
            --provenance-path myapp.intoto.jsonl \
            --source-uri github.com/myorg/myapp \
            --source-tag ${{ github.event.release.tag_name }}

      - name: Deploy
        if: success()
        run: |
          scp myapp deploy@prod-server:/opt/myapp/
          ssh deploy@prod-server "systemctl restart myapp"

If verification fails, the deploy step never runs. This prevents compromised artifacts from reaching production even if an attacker manages to publish a malicious release.

Signing Container Images with Cosign

For containerized workloads, combine SLSA provenance with cosign for image signing. The following workflow builds a container image, generates SLSA provenance, and signs the image using cosign with OIDC-based keyless signing:

name: container-release
on:
  push:
    tags: ["v*"]

jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      id-token: write
      packages: write
    outputs:
      image: ${{ steps.meta.outputs.tags }}
      digest: ${{ steps.build.outputs.digest }}
    steps:
      - uses: actions/checkout@v4

      - uses: docker/setup-buildx-action@v3

      - id: meta
        uses: docker/metadata-action@v5
        with:
          images: ghcr.io/myorg/myapp
          tags: type=semver,pattern={{version}}

      - id: build
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          provenance: mode=max
          sbom: true

      - name: Install cosign
        uses: sigstore/cosign-installer@v3

      - name: Sign image keyless
        run: |
          cosign sign --yes \
            ghcr.io/myorg/myapp@${{ steps.build.outputs.digest }}

      - name: Attach SLSA provenance
        run: |
          cosign attest --yes \
            --predicate <(echo '${{ steps.build.outputs.provenance }}') \
            --type slsaprovenance \
            ghcr.io/myorg/myapp@${{ steps.build.outputs.digest }}

Consumers verify both the signature and the attestation before pulling the image:

# Verify image signature
cosign verify ghcr.io/myorg/myapp:1.0.0 \
  --certificate-identity https://github.com/myorg/myapp/.github/workflows/container-release.yml@refs/tags/v1.0.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

# Verify SLSA provenance attestation
cosign verify-attestation ghcr.io/myorg/myapp:1.0.0 \
  --type slsaprovenance \
  --certificate-identity https://github.com/myorg/myapp/.github/workflows/container-release.yml@refs/tags/v1.0.0 \
  --certificate-oidc-issuer https://token.actions.githubusercontent.com

Best Practices for SLSA Adoption

Common Pitfalls to Avoid

One frequent mistake is generating provenance but storing it in a location separate from the artifact, making verification impractical. Always attach provenance directly to releases or as an attestation to container images. Another pitfall is using self-hosted runners for release builds, which immediately drops you below Level 2 because the build environment is not managed or isolated.

Teams also frequently forget to verify third-party dependencies. If your application pulls a library that was compromised upstream, your own SLSA Level 3 build does not protect you. Use tools like depbot or GitHub's dependency review action to check that your dependencies also provide provenance.

Conclusion

SLSA provides a practical, graduated framework for securing software supply chains against increasingly sophisticated attacks. By moving from Level 1 through Level 3, you transform your build pipeline from an opaque process into a verifiable, auditable system where every artifact can be traced to its source. The tools required—GitHub Actions, the SLSA generator, slsa-verifier, and cosign—are freely available and integrate cleanly into existing workflows. Start by generating provenance for your next release, add verification to your deployment pipeline, and incrementally harden your build platform until you reach Level 3. The investment pays off not just in security, but in the confidence that every artifact you ship is exactly what your source code intended it to be.

— Ad —

Google AdSense will appear here after approval

← Back to all articles