Reference incidents

Supply chain risk classifies the mechanisms. This page documents what actually happened.

The distinction is not cosmetic. A typology can be argued with; a dated compromise, with its vendor, its vulnerability identifier and its blast radius, cannot. It is the argument that unlocks a budget and makes an engineering constraint acceptable.

A reading caveat. Each entry points to a countermeasure. Do not conclude that it would have prevented the attack: in several cases nothing recommended here would have been enough. The column names what would have shortened detection or reduced the blast radius, which is already the essential part.

The series

Date Incident Mechanism What would have limited it
Jun 2017 M.E.Doc / NotPetya A vendor’s update channel compromised; propagation through a legitimate update Segmentation, least privilege on the update account
Sep 2017 CCleaner The vendor’s build environment compromised; the malicious binary carried a legitimate signature Provenance attestations, isolation of the build service
Nov 2018 event-stream (npm) The maintainer hands the project to a stranger, who adds a malicious dependency targeting a cryptocurrency wallet Watching for maintainer changes, review of new dependencies
Dec 2020 SolarWinds Orion (SUNBURST) Build system compromised; a backdoor inserted between verified source and distributed artefact Reproducible builds, provenance signed by the build service
Feb 2021 Dependency confusion Research showing a public package outranks an internal one of the same name, at several major vendors Reserved namespaces, explicit resolution priority, internal registry
Apr 2021 Codecov The coverage upload script is modified for months and exfiltrates the environment variables of every CI chain that runs it Script hash verification, least-privilege tokens, egress filtering
Jul 2021 Kaseya VSA Remote administration software used as a distribution vector for ransomware towards end customers Segmentation, control of administration channels
Oct 2021 ua-parser-js (npm) Maintainer account hijacked; malicious versions published on a very widely used package Strong authentication on publishing accounts, quarantine on new versions
Dec 2021 Log4Shell (CVE-2021-44228) Not a supply chain attack but a critical vulnerability in a ubiquitous component: the question becomes “where is it in your estate?” Centralised SBOM: an answer in minutes rather than weeks
Mar 2022 node-ipc Destructive code introduced deliberately by the maintainer, for political reasons Version locking, review before version bumps
Mar 2023 3CX Compromised installer — and a cascading compromise: the vendor had itself been hit through a trojanised third-party product Diligence on suppliers’ suppliers, signature verification
Mar 2024 xz / liblzma (CVE-2024-3094) Social engineering over more than two years to obtain maintainership, then a backdoor targeting the SSH service. CVSS 10 Very little — see below
Jun 2024 polyfill.io The domain serving a script to hundreds of thousands of sites is sold, then serves malicious code Self-hosting assets, subresource integrity checks
Mar 2025 tj-actions/changed-files (CVE-2025-30066) The version tags of a CI action are repointed to a malicious commit that writes secrets into the logs Pinning by hash rather than by tag, non-public logs

Five incidents that teach something precise

SolarWinds — a signature says nothing about content

The backdoor was inserted inside the build system, between intact source and a distributed artefact. The binary shipped was authentically signed by the vendor: any client-side signature check passed.

What it changes for you. Signing an artefact proves who published it, not what it contains nor how it was produced. That is exactly the gap that provenance attestations close: they describe the repository, the revision, the chain and the build parameters. A build service isolated so that user-defined steps cannot reach the signing material is the structural answer.

Codecov — CI is a target, not a neutral tool

A script downloaded and executed by thousands of integration chains was quietly modified, and for months exfiltrated the contents of environment variables — that is, the tokens, keys and credentials found there.

What it changes for you. Everything the build chain executes is part of the product. See Securing the build chain.

xz — the incident your countermeasures do not catch

Two years of legitimate contributions to earn the maintainer’s trust, then a backdoor concealed in the release archives — absent from the source repository — targeting SSH authentication. Found by accident, by an engineer puzzled by abnormal CPU usage.

What it changes for you. No diligence grid would have ruled this project out: it was active, maintained, widely audited and present in every distribution. Two lessons only, and they are modest:

  • the divergence between repository and published archive was the signal; building from source, or verifying that the archive matches the repository, would have exposed it;
  • a lone maintainer under pressure is a risk criterion, and it appears in your assessment grid.

It is worth saying plainly that an arrangement has limits. Claiming otherwise discredits the rest of it.

polyfill.io — the dependency nobody counts

A script loaded from a third-party domain appears in no manifest and in no SBOM generated from a package manager. Selling the domain was enough to turn an invisible dependency into a vector.

What it changes for you. Resources loaded at runtime from a third party are dependencies in their own right, to be inventoried and justified like any other. This site loads exactly one — the audience measurement served by its own host — and a build check fails if a second appears: the useful discipline is not promising zero, but being able to enumerate. It is one of the blind spots of the web frontend.

tj-actions — a tag is not a hash

The attacker did not need to publish a version: they repointed the existing tags to a malicious commit. Every chain referencing the action by tag executed the new code on its next build, and wrote its secrets into logs that are often public.

What it changes for you. Pinning by hash is not a refinement: it is the only immutable reference. See Locking and updating dependencies.

What the series shows

The point of compromise moves upstream. The older cases targeted the distribution channel; the recent ones target the build system, publishing accounts and CI tooling. The further upstream it goes, the more worthless the final product’s signature becomes, since it is applied after the compromise.

Detection time matters more than prevention. None of the fourteen was prevented by an automated control. Several were found by chance. What separates organisations is how long it took them to answer “are you affected?” — from minutes to weeks, depending on whether they had a centralised inventory.

The link to the Regulation is direct. Article 13(5) requires documented diligence on third-party components; Annex I, Part I requires limiting attack surfaces and protecting integrity. These fourteen cases describe precisely which surfaces need reducing.

This list is a selection, not an inventory. It keeps publicly documented, well-established incidents chosen to cover each mechanism in the typology. The most recent campaigns have industrialised some of these patterns — notably self-replicating worms on package registries, which abuse stolen publishing tokens to spread from package to package with no human involvement. Keeping this page current is part of the regulatory watch process.