Supply Chain Security SLSA: Protecting Your Software From Source to Deployment
In 2024, the xz utils backdoor nearly compromised every Linux server on the internet. A malicious contributor spent two years gaining trust before inserting a backdoor into a critical compression library. Supply chain security SLSA exists to prevent exactly this: ensuring that the software you deploy is exactly what you think it is, built from the source code you reviewed, on infrastructure you trust.
What Software Supply Chain Attacks Actually Look Like
Forget the abstract threat models. Here are the real attack vectors that have been exploited in production:
Dependency poisoning: An attacker publishes a package with a similar name to a popular one (lodash vs 1odash, or colors vs colours). Developers install the wrong one by typo. The malicious package includes legitimate functionality plus a data exfiltration payload. npm, PyPI, and Maven Central have all been hit.
Compromised maintainer accounts: Attackers gain access to a maintainer’s npm/PyPI account (phishing, credential stuffing) and publish a new version with malicious code. The event-stream incident (2018) is the textbook example — a new maintainer was given access and added a targeted payload.
Build system compromise: SolarWinds is the canonical example. Attackers infiltrated the build system and injected malicious code during compilation. The source code was clean — the binary wasn’t. This is the attack that SLSA is specifically designed to prevent.
CI/CD pipeline manipulation: Attackers modify GitHub Actions workflows to exfiltrate secrets, inject code during build, or replace artifacts after build but before deployment. A compromised action (like codecov/uploader) can affect thousands of repositories simultaneously.
SLSA Framework: Incremental Security Levels
SLSA (pronounced “salsa”) defines four levels of supply chain security, each building on the previous. The brilliance of SLSA is that you don’t need to achieve Level 4 immediately — each level provides meaningful security improvements:
Level 1: Provenance exists. Your build process generates a record of what was built, from which source, by which build system. This is the minimum: you can answer “where did this binary come from?” Currently, most organizations can’t answer this question for their own software.
Level 2: Hosted build service. Builds run on a managed service (GitHub Actions, Google Cloud Build) rather than developer laptops. This ensures builds are reproducible and not influenced by local machine state. Moreover, the build service generates signed provenance attestations.
Level 3: Hardened build platform. The build service is tamper-resistant. Build configurations come from source control (not editable during build), secrets are isolated between builds, and the provenance is non-forgeable. This is where most organizations should aim — it prevents the SolarWinds-type attack.
Level 4: Two-party review. All changes require review by two independent parties before being built. The build is fully hermetic (no network access during build). This is the gold standard but has significant operational overhead.
Supply Chain Security SLSA: Implementing Level 3 with GitHub Actions
# .github/workflows/release.yml
# SLSA Level 3 release workflow
name: Secure Release
on:
push:
tags: ['v*']
# CRITICAL: Minimal permissions — only what's needed
permissions:
contents: read
id-token: write # For Sigstore OIDC signing
attestations: write # For provenance generation
packages: write # For publishing
jobs:
build:
runs-on: ubuntu-latest
outputs:
digest: steps.hash.outputs.digest
steps:
# Pin ALL actions to full commit SHA — never use tags
# Tags are mutable: an attacker can point v3 to malicious code
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
# Verify no unexpected files in the repository
- name: Verify repository integrity
run: |
git verify-commit HEAD || echo "Warning: unsigned commit"
git diff --stat HEAD~1 HEAD
- name: Build
run: |
npm ci --ignore-scripts # Don't run postinstall scripts
npm run build
npm run test
- name: Generate artifact hash
id: hash
run: |
sha256sum dist/app.tar.gz | tee checksums.txt
echo "digest=$(sha256sum dist/app.tar.gz | cut -d' ' -f1)" >> "$GITHUB_OUTPUT"
# Sign the artifact with Sigstore (keyless — uses GitHub OIDC identity)
- uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0
- name: Sign artifact
run: cosign sign-blob --yes --output-signature dist/app.tar.gz.sig dist/app.tar.gz
- name: Upload artifact
uses: actions/upload-artifact@5d5d22a31266ced268874388b861e4b58bb5c2f3 # v4.3.1
with:
name: release-artifacts
path: |
dist/app.tar.gz
dist/app.tar.gz.sig
checksums.txt
# Generate SLSA provenance using the official generator
provenance:
needs: build
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.0.0
permissions:
actions: read
id-token: write
contents: write
with:
base64-subjects: |
${{ needs.build.outputs.digest }} dist/app.tar.gz
# Verify provenance before publishing
verify:
needs: [build, provenance]
runs-on: ubuntu-latest
steps:
- name: Verify SLSA provenance
uses: slsa-framework/slsa-verifier/actions/installer@v2.5.1
- run: |
slsa-verifier verify-artifact dist/app.tar.gz --provenance-path provenance.intoto.jsonl --source-uri github.com/company/appKey practices in this workflow:
- Pin actions to commit SHAs — Not tags. Tags are mutable references that an attacker can redirect. SHA-pinning ensures you always run the exact code you reviewed.
- Use –ignore-scripts with npm ci — postinstall scripts are a common attack vector in npm packages. Disable them and explicitly run only your own scripts.
- Sigstore keyless signing — No long-lived signing keys to manage or steal. The artifact is signed using your GitHub workflow’s OIDC identity, which is verifiable by anyone.
- SLSA provenance generation — The slsa-github-generator creates a tamper-proof record of what was built, from which source, by which workflow.
SBOM: Know What’s in Your Software
A Software Bill of Materials lists every component in your application — every direct dependency, every transitive dependency, and their versions. When a vulnerability is announced (like Log4Shell), an SBOM lets you answer “are we affected?” in seconds instead of hours.
Generate SBOMs at build time (not after deployment) because that’s when the full dependency graph is resolved. Tools like Syft, Trivy, and CycloneDX generate SBOMs in standard formats (SPDX and CycloneDX) that can be stored, queried, and compared across versions.
Practical Checklist: Start Here
- Today: Pin all GitHub Actions to commit SHAs (use StepSecurity’s pin-github-action tool to automate this)
- This week: Enable npm audit / pip audit in CI and fail builds on critical vulnerabilities
- This month: Add Sigstore signing to your release workflow
- This quarter: Implement SLSA Level 2 provenance generation
- Ongoing: Review dependency updates with Dependabot/Renovate and actually read the changelogs for critical dependencies
Related Reading:
Resources:
In conclusion, supply chain security SLSA isn’t optional — it’s the minimum standard for responsible software delivery. The xz utils attack showed that trust-based security doesn’t scale. Verification-based security through provenance, signing, and SBOMs does. Start with Level 1, aim for Level 3, and make each build verifiable.