Software Supply Chain Failures
Supply chain failures are not a patching problem. A03:2025 replaced the 2021 Vulnerable and Outdated Components category because the incidents that mattered were not "you ran an old library" — they were "an attacker decided what your build consumed". Log4Shell (CVE-2021-44228) arrived through a transitive dependency most affected organisations had never chosen. The question is different: can you prove what you shipped, and who could have changed it?
Namespace Trust and Typosquatting
Package managers resolve a name and a version. They do not resolve who published it. Dependency confusion abuses that gap. In 2021 Alex Birsan published packages under names harvested from private manifests, and more than 35 companies — Apple, Microsoft and PayPal among them — had build systems pull the public package in preference to their own internal one. The mechanism differs by ecosystem: npm falls back to the public registry for unscoped names, pip merges indexes and prefers the highest version, and Maven tries repositories in order.
Typosquatting is the same assumption with a spelling mistake. The 2017 crossenv campaign
registered dozens of packages named like popular build tools and read environment variables — credentials included —
from every machine that installed them. Detection starts with a diff: compare the lockfile's registry URLs
and integrity hashes with the previous release, and confirm internal names cannot resolve publicly.
Compromised Maintainers and Packages
A maintainer account is a publishing credential, and stealing one bypasses every review the package ever
passed. In 2018 the author of event-stream handed publish rights to an unknown account, which
added flatmap-stream; its payload read Copay wallet credentials. In October 2021 an attacker took
over the ua-parser-js npm account and published 0.7.29, 0.8.0 and 1.0.0 carrying a cryptominer
and a credential stealer. Those versions were live for roughly four hours. Neither incident has a CVE — the
package is not vulnerable, it is hostile — so a vulnerability scan reports nothing.
Danger
Build-Pipeline Compromise
The xz backdoor (CVE-2024-3094) settles the argument. The payload was not in the git repository. A modified
build-to-host.m4 existed only in the 5.6.0 and 5.6.1 release tarballs; at configure time it
decoded data hidden in the project's "test" fixtures and wrote it into the liblzma Makefile. The result linked
into sshd through libsystemd on x86-64 glibc systems. Anyone reviewing the repository saw nothing, and anyone
scanning dependencies saw nothing. The control that would have caught it is a build you can reproduce: rebuild
from the tagged source in an isolated builder and compare the resulting digest with the published artefact.
Build from source rather than vendor tarballs, keep the builder hermetic, and give it credentials that expire.
Provenance, Signing and SBOMs
Provenance turns that comparison into something you can automate. SLSA's build track defines three levels above
"no guarantees": L1 provenance exists and describes the build, L2 provenance is signed by a hosted build
platform, and L3 requires a hardened platform where runs cannot influence one another and build steps cannot
reach the signing key. Sigstore implements the signing side: keyless cosign binds an artefact
digest to a workload identity through a short-lived Fulcio certificate and records the signature in the Rekor
transparency log.
An SBOM is a different artefact. CycloneDX and SPDX list components and versions, which is useful for inventory and incident response — but an SBOM does not tell you whether the vulnerable code is reachable, whether the artefact you are running matches the SBOM, or whether that artefact was built from the source it claims. Those are attestation questions, not inventory questions.
Detect What You Actually Run
Start from the resolved tree rather than the manifest, then check it for known-bad and tampered packages. The first command tells you what is there; the second only what someone has already reported.
# What is installed, including transitive dependencies
npm ls --all
# Registry signatures and provenance attestations for what was installed
npm audit signatures
# Python: audit a resolved requirements file, or the active environment
pip-audit -r ./requirements.txt
pip-audit
# Whole-repository scan against OSV advisories (v2 CLI)
osv-scanner scan -r .
osv-scanner scan image registry.example.com/app:1.4.2
# Filesystem and container image, vulnerabilities only
trivy fs --scanners vuln .
trivy image --scanners vuln registry.example.com/app:1.4.2Log4Shell shows why the inventory must be resolved rather than declared: the vulnerable class arrived inside a logging library nobody on the team had chosen. MOVEit (CVE-2023-34362) is the opposite shape — one internet-facing product, mass exploitation, and patching that could not keep up.
Testing Checklist
- ☐ 1. Map every dependency source: public registry, private registry, git URL, container base image, vendored tarball
- ☐ 2. List package names that look internal (company prefixes, service names) and confirm they cannot resolve publicly
- ☐ 3. Check registry resolution order — npm scope, pip index configuration, Maven repository order
- ☐ 4. Diff the lockfile against the previous release; flag new packages, new registries and changed integrity hashes
- ☐ 5. Resolve the full tree, not the manifest — the transitive layer is where confusion and Log4Shell-class bugs live
- ☐ 6. Verify signed provenance or attestations where the ecosystem publishes them
- ☐ 7. Request the SBOM and compare it with what is actually running
- ☐ 8. Confirm the artefact was built by a pipeline the organisation controls, from a commit it can identify
Evidence Collection
Resolution Trace: Lockfile entry showing package name, version, source registry URL and integrity hash — plus the same entry from the previous release for comparison
Publisher Mismatch: Registry metadata showing a package name that resolves to a public publisher when the organisation expected a private one
Build Provenance: SLSA attestation or cosign verification output for the artefact, or the absence of one where the release claims it exists
SBOM Delta: Components present in the running artefact but absent from the supplied SBOM, or versions that do not match
CVSS Range: Supply chain compromise: 8.0–10.0 (Critical) once publish or build access is achieved — impact follows the compromised component
Remediation
- • Control the namespace: scope internal packages (
@company/…), pin registry order, and block public resolution of internal names. - • Verify provenance: require signed attestations for release artefacts and fail closed when verification fails.
- • Build reproducibly: build from tagged source in an isolated builder, compare output digests, and never publish from a developer workstation.
- • SBOM per release: generate CycloneDX or SPDX alongside the artefact, store both together, and diff between releases.
- • Short-lived credentials: replace long-lived registry tokens with OIDC-issued, per-build credentials.
- • Monitor the namespace: alert on new publishes to organisation scopes and on lockfile changes pointing at unknown registries.
False Positive Identification
- Version match without reachability: A vulnerable version in the tree does not mean the vulnerable code is called — confirm the affected function is on a reachable path before rating it.
- Backported patches: Distributions backport fixes without changing the upstream version number; check the vendor changelog before reporting.
- Build-only dependencies: A hostile package in devDependencies that never ships is still a build-pipeline risk, but it is not a vulnerability in the delivered artefact.
- SBOM component ≠ loaded code: An SBOM entry proves a component is present in the inventory, not that it is loaded at runtime or reachable from user input.
- Private name that resolves privately: A package whose name exists publicly is only a dependency-confusion finding if the build would prefer the public copy.
Supply Chain Practice
Audit a resolved dependency tree, trace where each package came from, and verify what you can.