Software Supply Chain Security
Overview
The software supply chain encompasses everything between writing source code and deploying running software: dependencies, build tools, CI/CD pipelines, artifact storage, and deployment mechanisms. Attacks on the supply chain—SolarWinds (2020), Codecov (2021), Log4Shell (2021), ua-parser-js (2021), xz-utils (2024)—have made supply chain security a top priority. This chapter covers the frameworks, tools, and practices for securing the software supply chain.
Artifact Provenance
Provenance is a verifiable record of how an artifact was produced: what source code, build environment, toolchain, and build steps were used. Provenance enables consumers to verify that an artifact was built from the claimed source by an authorized process.
SLSA (Supply-chain Levels for Software Artifacts)
SLSA is a security framework with four levels of increasing assurance:
| Level | Name | Guarantee | Requirements |
|---|---|---|---|
| 1 | Documented build | Provenance exists | Provenance metadata (who, what, how) |
| 2 | Hosted build | Tamper-proof build | Hermetic build platform, authenticated provenance |
| 3 | Hardened build | Change-protected build | Non-falsifiable provenance, build platform security |
| 4 | Hermetic + reproducible | Maximum assurance | Two-party review, reproducible builds, hermetic |
SLSA Level 3 requires non-falsifiable provenance—the provenance must be generated by the build platform itself (not the developer) and signed by a key the developer cannot access. This prevents tampering with the provenance after the build.
In-Toto
in-toto provides a framework for verifying the integrity of the software supply chain as a whole (not just individual artifacts):
- Layout: defines the expected supply chain steps (fetch, build, test, package) and authorized functionaries
- Attestations: each step produces a signed metadata file describing what it did
- Verification: consumers verify the full chain—every step was performed by an authorized party, in the correct order, with the expected inputs
Source → fetch (attestation) → build (attestation) → test (attestation) → package (attestation)
│
Verify chain:
- All steps signed
- Correct order
- Expected functionaries
SBOM (Software Bill of Materials)
An SBOM is a comprehensive list of all components in a software package—libraries, frameworks, dependencies, versions, licenses, and relationships. It is essential for vulnerability management and incident response.
SBOM Standards
| Standard | Format | Adoption |
|---|---|---|
| SPDX | JSON/XML, ISO/IEC 5962:2021 | Linux Foundation, widely adopted |
| CycloneDX | JSON/XML, OWASP project | Growing adoption, lightweight |
| SWID | XML, ISO/IEC 19770-2 | Enterprise, Microsoft ecosystem |
An SBOM should include: component name, version, supplier, unique identifier (CPE, PURL), relationships (depends-on, contains, patches), license information, and vulnerability references (CVE IDs).
U.S. Executive Order 14028 (May 2021) mandates SBOMs for all software sold to the federal government, dramatically accelerating adoption.
SBOM in Practice
- Generation: Syft (Anchore), Trivy, Microsoft SBOM Tool, SPDX tool
- Consumption: Grype (vulnerability matching), dependency-track (OWASP), GUAC (Google’s supply-chain graph)
- Storage: VEX (Vulnerability Exploitability eXchange) — SBOM + vulnerability status
Sigstore & Cosign
Sigstore provides a free, open-source code-signing infrastructure:
- Cosign: sign and verify container images and artifacts using keyless signatures (OIDC-based, no key management)
- Fulcio: certificate authority that issues short-lived certificates based on OIDC identity (e.g., GitHub Actions identity)
- Rekor: transparency log for signatures—every signature is publicly recorded, enabling detection of rogue certificates
Sign:
Developer → OIDC auth (GitHub) → Fulcio (issues certificate) → Cosign (signs image) → Rekor (logs signature)
Verify:
Verifier → Rekor (check transparency log) → Fulcio (verify certificate chain) → Cosign (verify signature)
Key innovation: keyless signing eliminates the key distribution problem. Identity is bound to the signing event through OIDC (GitHub Actions, Google identity, etc.), not through pre-shared keys.
Binary Transparency & Package Signing
Binary Transparency
Modeled on Certificate Transparency (CT), binary transparency ensures that compiled binaries are publicly logged, enabling detection of unauthorized builds:
- Grafeas: Google’s artifact metadata API for storing and querying attestation metadata
- Binary Transparency (note): academic proposal; production systems use Sigstore’s Rekor as a general-purpose transparency log
Package Signing
- npm: package provenance via Sigstore (built into npm publish)
- Python: Trusted Publishing (OIDC-based, Sigstore-backed) on PyPI
- RubyGems: Sigstore-based signing for gems
- OCI registries: Cosign for container image signing
Attack Vectors
Dependency Confusion
Dependency confusion exploits package managers that prefer private (internal) packages over public ones. An attacker publishes a malicious package on a public registry with the same name as an internal package; when a build resolves dependencies, it may accidentally fetch the public (malicious) version instead of the private one.
Mitigation: namespace reservation, scoped packages (e.g., @company/package), registry configuration that prefers internal sources, and build tool flags that enforce internal-only resolution for internal packages.
Typosquatting
Attackers publish packages with names similar to popular libraries (e.g., reqeusts instead of requests). Automated tooling and IDE autocomplete can inadvertently install these.
Mitigation: lockfiles with integrity hashes, allow-lists of approved packages, namespace scoping, and monitoring (Socket.dev, PyPI advisor).
Malicious Dependencies
Supply chain attacks where a maintainer account is compromised or a legitimate maintainer introduces malicious code:
- Account takeover: steal credentials of library maintainers (e.g., event-stream, ua-parser-js, xz-utils)
- Maintainer malice: maintainer intentionally introduces backdoors
- Transitive dependency attacks: compromise a widely-depended-upon library
Compiler Supply Chain
The ultimate supply chain attack: compromise the compiler itself. Reflections on Trusting Trust (Thompson, 1984) demonstrated that a compromised compiler can inject backdoors into compiled programs, and the backdoor survives even if you recompile the compiler from clean source.
Mitigation: bootstrappable builds (e.g., Guix’s bootstrappable packages), diversifying compilers (different compilers for different builds), and reproducible builds (compare output from independent build environments).
Dependency Risk Scoring & SCA
Software Composition Analysis (SCA) tools scan dependencies for known vulnerabilities:
| Tool | Scanning Approach | Integration |
|---|---|---|
| Snyk | Vulnerability DB + license compliance | CI/CD, IDE, CLI |
| Dependabot | GitHub-native, auto-PR for updates | GitHub |
| Trivy | Container + filesystem + git scanning | CI/CD, Kubernetes admission |
| Grype | SBOM-based vulnerability matching | CI/CD |
| Socket.dev | Behavioral analysis (not just CVEs) | npm, PyPI |
Risk scoring combines multiple signals: known vulnerabilities (CVEs), maintainer activity, dependency staleness, license compatibility, and behavioral indicators (does the package access network, file system, or environment variables unexpectedly?).
Vulnerability Reachability
Not all vulnerabilities in an SBOM are exploitable. Reachability analysis determines whether a vulnerable function is actually called by your application:
- If a library has a CVE in function X, but your code never calls X, the vulnerability is not reachable
- Reachability reduces false positives by 80–95% compared to naive CVE matching
- Tools: Snyk Code, GitHub Advisory Database (with reachability), Semgrep Supply Chain
Secure CI/CD
Ephemeral Runners
CI runners should be ephemeral: created fresh for each build, destroyed after completion. This prevents:
- Cross-contamination: secrets or artifacts from one build leaking into another
- Persistent compromise: an attacker who compromises a runner gains nothing persistent
- State accumulation: no stale caches, no leftover build artifacts
Isolated & Sandboxed Builds
Build steps should run in sandboxed environments:
- Container isolation: each step in its own container
- Network restrictions: builds can only access declared network endpoints
- Secret injection: secrets injected at runtime via OIDC, not baked into images
- No self-hosted runners for open source: use ephemeral cloud runners to prevent persistent supply chain compromise (GitHub’s Open Source CI hardening)
Secure Pipeline Patterns
┌──────────────────────────────────────────────────────┐
│ Secure CI/CD Pipeline │
│ │
│ Source → Lint → Build → Test → Sign → Scan → Deploy │
│ │ │ │ │
│ provenance Cosign SBOM + Attest │
│ attestation (Sigstore) vulnerability │
│ check │
│ │
│ Principles: │
│ - Least privilege (narrow permissions per step) │
│ - Ephemeral runners (no persistent state) │
│ - OIDC-based identity (no long-lived secrets) │
│ - Hermetic builds (no host access) │
│ - Signed + attested artifacts │
└──────────────────────────────────────────────────────┘
Artifact Registries & OCI Artifacts
OCI (Open Container Initiative) Artifacts
The OCI image spec was extended to support arbitrary artifacts beyond container images:
- Helm charts: stored as OCI artifacts in registries
- SBOMs: attached as referrers to images
- Attestations: Sigstore attestations linked to images via digests
- WASM modules: OCI-stored WebAssembly components
This convergence means a single registry (Docker Hub, ECR, GCR, GAR, GHCR) can store all artifact types with a unified signing and verification workflow.
WASM Package Ecosystems
WebAssembly packages are emerging as a language-agnostic distribution format:
- wasm-pkg: OCI-native WASM component registry
- Warg: bytecode alliance’s package registry for WASM components
- Registries: store compiled WASM components with metadata, versioning, and OCI-compatible signing
WASM components are language-independent (can be produced from Rust, C++, Go, AssemblyScript, etc.) and run in any WASM runtime (Wasmtime, WasmEdge, wasmer, browsers).
Interview Angle
“How would you secure a CI/CD pipeline against supply chain attacks?”
Layer defenses: (1) ephemeral runners with no persistent state, (2) OIDC-based identity (no secrets stored in CI), (3) hermetic builds in sandboxed containers, (4) signed artifacts with Sigstore/Cosign, (5) SBOM generation at build time, (6) vulnerability scanning with reachability analysis before deployment, (7) provenance attestation linking artifact back to source commit, (8) allow-lists for dependencies (no new packages without review), (9) branch protection requiring review before builds are promoted to production artifacts.
“Explain dependency confusion and how to prevent it.”
Dependency confusion exploits the priority order of package managers. If an internal package @myco/auth-lib exists only in your private registry, and an attacker publishes auth-lib or @myco/auth-lib on the public registry, a build that checks the public registry first will install the malicious version. Prevention: (1) scoped packages with unique org prefixes, (2) configure package managers to check private registries first, (3) use registry allow-lists that reject unapproved packages, (4) monitor public registries for your package names (automated alerts).
Key References
- SLSA specification (slsa.dev)
- Sigstore documentation (sigstore.dev)
- in-toto attestation framework (in-toto.io)
- SPDX specification (spdx.dev)
- Thompson, “Reflections on Trusting Trust” (1984)
- U.S. Executive Order 14028 on Improving the Nation’s Cybersecurity (2021)