Software Supply Chain Attacks and the SBOM Response

A security analyst inspecting code on a computer screen

Software supply chain attacks work because build systems are trusting by design: a package manager resolves a name, a CI runner executes whatever install script arrives, and nobody looks. The response, an inventory format plus a provenance framework plus a signing service, is now mature enough to be boring. Most small teams still have none of it, and the gap between the two facts is the interesting part.

The incidents that defined the category

SolarWinds, December 2020. Attackers compromised the build pipeline for the Orion network management platform and shipped a signed backdoor to customers. CISA issued Emergency Directive 21-01 ordering federal agencies to disconnect affected Orion products. In a filing dated 14 December 2020, SolarWinds said fewer than 18,000 customers may have used a compromised version, out of roughly 33,000 Orion customers it notified. The trojanised updates were distributed between March and June 2020, which is the point: the dwell time in a build system is measured in months, not days.

Dependency confusion, February 2021. Alex Birsan published private-sounding package names to public registries with high version numbers and let package managers resolve to his copies instead of the companies’ internal ones. Sonatype’s write-up records that the technique reached more than 35 organisations, including Microsoft, Uber, Tesla, Yelp and Shopify, and earned over $130,000 in bug bounties. Unlike typosquatting, it needs no developer mistake. If your build resolves a private scope from a public registry, you are exposed by default.

Codecov, April 2021. Codecov’s own post-mortem states that the attacker extracted a Google Cloud Storage HMAC key from an intermediate layer of Codecov’s public Docker image, then used it to modify the Bash Uploader served to customers so it exfiltrated git remote URLs and environment variables. Discovered 1 April 2021 and disclosed on 15 April, it turned every CI environment variable in every affected pipeline into attacker-readable data.

xz utils, March 2024. A backdoor in xz utils 5.6.0 and 5.6.1, inserted in February 2024 by a maintainer operating as “Jia Tan,” was found on 29 March 2024 by Andres Freund after he noticed SSH connections burning unexpected CPU. It carried CVE-2024-3094 with a CVSS score of 10.0. The social engineering that produced the maintainer’s commit access ran for over two years through sock puppet accounts pressuring the original maintainer. No SBOM would have caught it, and that is worth sitting with.

npm, September 2025 onward. On 8 September 2025 a phishing email from the lookalike domain npmjs.help harvested maintainer credentials and led to malicious releases of 18 widely used packages including chalk, debug and ansi-styles, carrying a browser-side cryptocurrency wallet drainer. Vercel’s incident write-up logs response starting at 17:39 UTC and cache purging complete by 22:19 UTC the same day, across 76 affected projects. Two weeks later CISA warned of a widespread compromise affecting over 500 npm packages, this time from a self-replicating worm since named Shai-Hulud that stole GitHub personal access tokens and AWS, GCP and Azure keys, published them to public repositories, and republished itself into further packages using the credentials it found. CISA’s advice included pinning to versions published before 16 September 2025 and rotating every developer credential.

Two mechanisms worth understanding precisely

Dependency confusion

Most package managers resolve a bare name against a list of configured registries and pick the highest satisfying version. Publish acme-internal-auth at version 99.0.0 on the public registry and a developer whose .npmrc lists both the private and public registry may get yours. The fixes are configuration, not tooling: scope private packages and bind that scope to the private registry only; disable public fallback for private scopes; and pre-register your internal names publicly so nobody else can claim them.

Typosquatting

Typosquatting relies on a human error, a transposed character or a plausible-sounding alternative name, and remains a steady background rate across npm, PyPI and RubyGems rather than a single event. It is a weaker attack than dependency confusion and easier to defend: lockfiles, a review requirement for any new direct dependency, and a registry proxy that blocks packages younger than a set age.

The SBOM response, and what it does not do

An SBOM is a machine-readable inventory of the components in a build. Two formats matter. SPDX is recognised as an international standard, ISO/IEC 5962:2021, and reached version 3.0.1 in December 2024. CycloneDX, an OWASP project, was standardised by Ecma International as ECMA-424. Both are widely produced by build tooling; the practical choice usually follows whichever your scanner and your customers already consume.

CISA replaced the 2021 NTIA minimum elements with a 2026 update, published in July 2026 and co-authored with cybersecurity agencies from more than a dozen countries including Canada. The 2026 Minimum Elements for a Software Bill of Materials promote several previously optional fields to mandatory, notably component hash value and algorithm and component licence, and add the SBOM tool name and the generation context, meaning whether the SBOM was produced pre-build or post-build. Terminology changed too: “supplier” became “software producer” and “author of SBOM data” became “SBOM author.” The direction of travel is away from a document you attach to an RFP and towards a machine-readable record used continuously.

What an SBOM does not do is verify anything. It tells you what you think is in a build. xz utils would have appeared in every SBOM of every affected distribution, correctly named and correctly versioned, with a valid hash. Inventory answers “am I affected,” which is the Log4Shell question, and it answers it fast. It does not answer “was this artefact built from the source it claims.”

Provenance: SLSA and sigstore

That second question is what SLSA addresses. The SLSA v1.0 build track defines four levels:

Level Requirement Adversary it stops
Build L0 Nothing; the absence of SLSA None
Build L1 Provenance exists, showing build platform and inputs Mistakes only; “trivial to bypass or forge”
Build L2 Hosted build platform, digitally signed provenance, downstream verification Forgery “requires an explicit attack, though this may be easy to perform”
Build L3 Hardened platform, builds cannot influence each other, signing secrets unreachable from user-defined build steps Forgery requires “exploiting a vulnerability beyond most adversaries’ capabilities”

SLSA v1.2, released on 24 November 2025, keeps the build track and adds a Source Track covering how code is authored, reviewed and managed, with Build Environment and Dependency tracks still in development. The Source Track is the piece aimed squarely at the xz pattern.

Sigstore supplies the signing. Cosign issues short-lived certificates against an OIDC identity, so a GitHub Actions workflow signs as itself rather than as a long-lived key somebody has to store, and records the signature in the Rekor transparency log. Registry-side enforcement has followed: npm disabled classic token creation on 5 November 2025 and revoked existing classic tokens on 9 December 2025, pushing publishers towards OIDC trusted publishing, which is the same primitive.

The first five things a small team should do

  1. Lock the resolver. Commit lockfiles, use npm ci or the equivalent, scope private packages to the private registry, and turn off public fallback for those scopes. This closes dependency confusion for roughly a day’s work.
  2. Kill install-time script execution where you can, and put a maturity delay in front of new versions through a registry proxy. Both npm 2025 incidents were caught in hours; a 48-hour quarantine would have absorbed most of the blast radius.
  3. Move publishing to OIDC trusted publishing and delete every long-lived registry token. Then enforce phishing-resistant MFA on maintainer accounts, because the September 2025 compromise was a phishing email, not a code flaw.
  4. Generate an SBOM in CI and store it with the artefact. Whichever format your scanner reads. Include hashes now that CISA’s 2026 elements require them. The value arrives the next time a critical CVE lands and you need an answer in minutes.
  5. Reach SLSA Build L2 by signing provenance in CI with cosign and verifying it at deploy. Hosted CI plus keyless signing gets most teams there without new infrastructure.

What none of that buys is protection against a trusted maintainer turning hostile, which is the xz scenario and the hardest problem in the field. Reducing direct dependency count, pinning transitive versions, and reading the diff on any dependency you actually depend on remain the only real mitigations. Inventory tells you where you stand; provenance tells you where an artefact came from; neither tells you whether the human who wrote the code meant well.

Sources

Post Comment