Back to glossary

Supply Chain Attack

A supply chain attack compromises a trusted supplier, like a package, build pipeline, or update server, so its own channel delivers malicious code to users.

Also known as software supply chain attack, supply-chain attack, supply chain compromise, open source supply chain attack, T1195

A supply chain attack, also called a software supply chain attack or supply chain compromise, is an attack on a trusted supplier, such as an open source package, a build pipeline, or an update server, that turns the supplier's own distribution channel into the delivery mechanism. Victims install the malicious code themselves, through a channel they already trust.

The mechanism is delegated trust. A package manager, an auto-updater, or a CI runner fetches whatever a supplier publishes and runs it with the consumer's own privileges, so the attacker never has to break into the victim. One malicious release reaches every consumer whose next install or update resolves to it. In 2025 and 2026, most malicious npm releases ran their payloads during installation, before any application code loaded and before any advisory existed.

The fact that matters most is speed. The xz-utils backdoor sat in release tarballs for 34 days before anyone reported it. The malicious axios release in March 2026 was publicly reported in two and a half hours, and that was still long enough. Our position: a supply chain attack has to be stopped where code is fetched and executed, by analyzing what the artifact actually does. CVE scanning cannot do that, because malware rarely gets a CVE. Signatures and provenance cannot do it alone, because the 2026 worms published through their victims' own legitimate pipelines.

What is a supply chain attack?

#

A supply chain attack reaches its victims through something they obtained from a supplier, not through their own systems. MITRE ATT&CK catalogs it as technique T1195, Supply Chain Compromise, and defines it in one sentence: "Adversaries may manipulate products or product delivery mechanisms prior to receipt by a final consumer for the purpose of data or system compromise."

MITRE splits the technique by supplier type: software dependencies and development tools (T1195.001), software products and their update channels (T1195.002), and hardware (T1195.003). This entry covers the first two, because that is where the volume is. A hardware implant needs physical access to a factory or a shipment. A software implant needs one publish credential, and every link of the software supply chain holds one.

Two boundaries are worth drawing. A vulnerable dependency, such as Log4Shell in 2021, is a supply chain risk but not a supply chain attack: nobody planted the bug. A new malicious package published under a lookalike name, such as a typosquat, attacks the consumer's choice of supplier rather than a supplier they already trust. We count both typosquats and hijacked releases as supply chain attacks, because in both cases the registry delivers the payload. Hijacked releases do more damage, because version ranges install them for you.

How does a supply chain attack work?

#

A supply chain attack works by taking control of one link that consumers have already authorized to send them code, then waiting for their tools to fetch and run it. That authorization is built into ordinary dependency management, for good reasons.

Modern tooling delegates three decisions to suppliers. The first is which code you get: a version range, an auto-updater, or a floating container tag lets the supplier choose the next version. The second is when it runs: npm lifecycle scripts, Python startup hooks, Cargo build scripts, and CI steps execute supplier code during install, startup, or build. The third is with what privileges: the installing user's, which on a CI runner include deploy keys and publish tokens. Each delegation exists to save work. Ranges deliver bug fixes without a manual upgrade, install scripts compile native modules, and CI needs credentials to ship.

A single line of a manifest shows the first delegation:

JSON
{
  "dependencies": {
    "axios": "^1.14.0"
  }
}

On March 31, 2026, that range resolved to the malicious axios@1.14.1 on any fresh install. Nothing in the consumer's repository changed. The new version added one dependency, plain-crypto-js@4.2.1, whose postinstall script dropped a remote access trojan.

Every supply chain attack answers three questions in order, and each answer is a place where a defense can act:

  • Entry: which trusted link the attacker controls. Usually a maintainer account, a maintainer's identity, a build system, or a hosted script or CI action.
  • Reach: how the payload travels from that link to victims. Range resolution, signed auto-updates, mutable tags and floating images, or self-propagation.
  • Trigger: when the victim's own tooling executes it. An install hook, interpreter startup, a build or CI step, or the application's runtime.

1 · Supplier side

Entry

Which trusted link does the attacker control?

  • Maintainer accountQix phishing, axios, ua-parser-js
  • Maintainer identityevent-stream handoff, xz-utils co-maintainer
  • Build or CI systemSolarWinds build, TanStack runner, LiteLLM release workflow
  • Hosted script or actionCodecov uploader, tj-actions tags

2 · Distribution

Reach

How does the payload get from that link to victims?

  • Version range resolves to it^1.14.0 → axios 1.14.1
  • Signed auto-updateSolarWinds Orion, fewer than 18,000 customers
  • Mutable tag or floating imagetj-actions v1…v45.0.7, LiteLLM :latest
  • Self-propagationShai-Hulud, Mini Shai-Hulud, Miasma

3 · Consumer side

Trigger

When does the victim's own tooling run it?

  • Install hookpreinstall, postinstall, binding.gyp, git prepare
  • Interpreter startupLiteLLM .pth and sitecustomize.py
  • Build or CI stepxz tarball m4, Codecov, tj-actions
  • Runtime in productionSUNBURST, Qix wallet hooks in browsers
Figure 1. Every supply chain attack combines an entry point, a reach mechanism, and a trigger. Examples are the incidents in the table below.

Figure 1 places the three stages along the path code travels, from supplier to distribution to consumer. Entry happens on the supplier side: a maintainer account (the Qix compromise, axios, ua-parser-js), a maintainer's identity (the event-stream handoff, the xz-utils co-maintainer), a build or CI system (SolarWinds, the TanStack compromise, the LiteLLM compromise), or a hosted script or action (Codecov, the tj-actions/changed-files compromise). Reach happens in distribution: a version range resolving to the new release (^1.14.0 to axios 1.14.1), a signed auto-update (SolarWinds Orion, fewer than 18,000 customers), a mutable tag or floating image (tj-actions v1 through v45.0.7, LiteLLM's latest tag), or self-propagation (the Shai-Hulud worm, Mini Shai-Hulud, and Miasma). The trigger happens on the consumer side: an install hook (preinstall, postinstall, binding.gyp, a git dependency's prepare script), interpreter startup (LiteLLM's .pth and sitecustomize.py files), a build or CI step (the xz tarball's m4 file, Codecov, tj-actions), or runtime in production (SUNBURST, the Qix wallet hooks in browsers). Supplier controls act on entry, registry and resolver controls act on reach, and consumer controls act on the trigger.

Supply chain attacks from event-stream to LiteLLM

#

The twelve incidents below cover each entry point, reach mechanism, and trigger in Figure 1. Dates are the first malicious release.

DateAttackEntryReachTrigger
Sep 2018event-streamMaintainer handoff: a volunteer got publish rights, then added the flatmap-stream dependencySemver range on a transitive dependencyBuild time, only inside the Copay wallet's release build
Mar 2020SolarWinds SUNBURSTCovert access to the Orion build processSigned vendor updates installed by fewer than 18,000 customersRuntime, inside the updated Orion product
Jan 2021Codecov Bash UploaderCredential extracted from a Docker image, used to alter the scriptCI pipelines downloading the script on every runEach CI run, which sent environment variables to the attacker
Oct 2021ua-parser-jsHijacked npm accountSemver ranges on three release linespreinstall.js, dropping a Monero miner and a password stealer
Feb 2024xz-utilsA co-maintainer with commit and release rightsLinux distribution packages built from release tarballsBuild, through m4 files absent from Git, then runtime in sshd
Mar 2025tj-actions/changed-filesA likely stolen credential, used to repoint tags v1 through v45.0.7Mutable tags referenced by more than 23,000 repositoriesInside CI, printing secrets to workflow logs
Sep 2025chalk, debug, and 17 morePhishing email for a fake npm 2FA resetPackages with 2 to 3 billion weekly downloadsRuntime, in end users' browsers, swapping crypto wallet addresses
Sep 2025Shai-HuludStolen npm tokensSelf-propagation into each victim maintainer's other packagespostinstall, running TruffleHog to harvest secrets
Mar 2026axiosHijacked maintainer account, publishing outside the normal release workflowCaret ranges on a package with 100 million weekly downloadspostinstall in the new plain-crypto-js dependency
May 2026Mini Shai-Hulud via TanStackCI: a pull_request_target workflow, cache poisoning, and an OIDC token read from runner memoryTrusted publishing, with valid Sigstore provenanceA git dependency's prepare script
Jun 2026Miasma "Phantom Gyp"Compromised maintainer credentialsWorm across npm, PyPI, GitHub Actions, and Go sourcenpm's implicit node-gyp rebuild of a binding.gyp file
Oct 2026LiteLLM 1.107.0A leaked key, then a stolen GitHub org tokenPyPI wheels and floating container tagsPython interpreter startup through a .pth file

Sources: npm on event-stream, CISA ED 21-01, SolarWinds' SEC filing, Codecov, CERT-EU on ua-parser-js, and Socket's reports linked below.

Three patterns run down the columns.

The entry column is mostly people and credentials, not code bugs. The event-stream and xz-utils attackers earned maintainer trust. The Qix compromise started with a 2FA-reset email from support@npmjs.help. The axios, ua-parser-js, and Shai-Hulud cases were maintainer account takeovers. The exceptions are CI misconfigurations, such as Codecov's Docker image and TanStack's pull_request_target workflow, and those exposed credentials too.

The trigger column moved to install time. Five of the six npm incidents in the table since 2021 executed during npm install, through install scripts or the implicit node-gyp build. Install time is where most supply chain attacks execute, and default-on dependency lifecycle scripts were a design mistake. pnpm 10 corrected it on January 7, 2025, and npm 12 on July 8, 2026, according to the npm registry's publish times for both releases. Neither fix reaches the other triggers: the Qix payload ran in browsers, LiteLLM ran at Python startup, and tj-actions ran inside CI.

The reach column needs no mistake from the victim. Every row used a mechanism working as designed: a caret range, a signed update, a tag, a transitive dependency nobody chose directly. That is the defining property of a supply chain attack, and the reason "be careful what you install" is not a defense.

Supply chain attacks went from months to hours, but only the loud ones

#

Supply chain attacks that once stayed hidden for months are now reported within hours, as long as they are loud. Figure 2 measures each incident from the first malicious release to the first report we can date from a primary source.

2018 to 20242025 to 2026Log scale
event-streamSep 2018
78 d
SolarWinds SUNBURSTMar 2020
~9 mo
Codecov uploaderJan 2021
60 d
ua-parser-jsOct 2021
4 h
xz-utilsFeb 2024
34 d
chalk and debugSep 2025
2.1 h
Shai-HuludSep 2025
3.0 h
axiosMar 2026
2.5 h
LiteLLM 1.107.0Oct 2026
2.0 h
Figure 2. Time from the first malicious release to the first report we can date from a primary source. Endpoints for each bar are listed in the table below.
AttackFirst malicious releaseFirst dated reportWindow
event-streamevent-stream@3.3.6, Sep 9, 2018 08:28 UTC (npm registry)npm security team notified, Nov 26, 201878 days
SolarWinds SUNBURSTOrion updates from March 2020 (SEC 8-K)CISA Emergency Directive 21-01, Dec 13, 2020About 9 months
Codecov Bash UploaderJan 31, 2021 (Codecov)Customer report, Apr 1, 202160 days
ua-parser-js0.7.29, Oct 22, 2021 12:15 UTC (npm registry)Clean 0.7.30 published 16:16 UTC4 hours
xz-utilsv5.6.0 tagged Feb 24, 2024Public disclosure, Mar 29, 202434 days
chalk and debugdebug@4.4.2, Sep 8, 2025 13:12 UTC (npm registry)Socket report, 15:16 UTC2.1 hours
Shai-Hulud@ctrl/tinycolor@4.1.1, Sep 15, 2025 19:52 UTC (npm registry)Socket report, 22:50 UTC3.0 hours
axiosaxios@1.14.1, Mar 31, 2026 00:21 UTC (npm registry)Socket report, 02:52 UTC2.5 hours
LiteLLM 1.107.0PyPI wheels, Oct 21, 2026 11:39 UTCSocket report, 13:41 UTC2.0 hours

For the npm and PyPI rows, the endpoint is Socket's published report, so each window is an upper bound. Automated alerts came sooner. Socket flagged plain-crypto-js@4.2.1 six minutes after it was published, before axios@1.14.1 existed, and flagged the LiteLLM wheels seven minutes after publication.

Read the chart as two populations, not a trend line. The long windows belong to quiet, targeted payloads. event-stream activated only inside one wallet's release build, and the xz backdoor only inside sshd on systems that linked it. The short windows belong to loud ones. A worm or a credential stealer that wants reach has to publish widely and fast, and fast publishing is exactly what publish-time scanners watch. ua-parser-js sits with the fast group even in 2021, and it dropped a cryptominer, which is about as loud as malware gets. The lesson is not that detection got better at everything. We see no evidence that a patient, targeted attacker is caught faster in 2026 than in 2024.

Short windows also do not mean small exposure. An install-time payload executes on the first fresh install, and CI fleets install continuously. A few hours of caret ranges resolving to axios@1.14.1 was enough to reach CI pipelines through npx and transitive ranges, as our analysis of the hidden blast radius of the axios compromise showed.

The npm timestamps above are reproducible. The registry keeps a version's publish time in the package document's time field even after the version itself is removed:

Bash
for spec in event-stream@3.3.6 ua-parser-js@0.7.29 chalk@5.6.1 \
            @ctrl/tinycolor@4.1.1 axios@1.14.1; do
  pkg=${spec%@*}; ver=${spec##*@}
  curl -s "https://registry.npmjs.org/${pkg/\//%2f}" |
    jq -r --arg p "$pkg" --arg v "$ver" \
      '[$p, $v, .time[$v], (.versions | has($v))] | @tsv'
done

Output on October 26, 2026. The last column says whether the registry still serves the version:

Plain text
event-stream	3.3.6	2018-09-09T08:28:59.503Z	false
ua-parser-js	0.7.29	2021-10-22T12:15:21.378Z	false
chalk	5.6.1	2025-09-08T13:13:05.239Z	false
@ctrl/tinycolor	4.1.1	2025-09-15T19:52:46.624Z	false
axios	1.14.1	2026-03-31T00:21:58.168Z	false

The record has gaps. When npm replaces a whole package with a security placeholder, its history goes too: flatmap-stream, which carried the event-stream payload, now lists only two timestamps, from November 28 and 29, 2018, and none for the release that carried the payload. PyPI keeps less. On the same day, the JSON API for litellm 1.107.0 returned HTTP 404, and the release no longer appears in the project's version list. If you are reconstructing your own exposure after an incident, capture registry metadata while it still exists.

Why are software supply chain attacks increasing?

#

Software supply chain attacks are increasing because each successful one now funds the next. Four changes made that true.

One publish is a broadcast. The 19 packages hijacked in the Qix compromise receive 2 to 3 billion downloads a week combined. axios receives about 100 million. Few initial-access techniques give an attacker that many execution points for one stolen credential.

Stolen credentials compound. Install-time payloads run on exactly the machines that hold publishing credentials: developer laptops and CI runners. The Shai-Hulud worm of September 2025, the first known self-propagating npm worm, used the npm tokens it stole to republish itself into its victims' other packages. When Australian police arrested two alleged members of TeamPCP in August 2026, the Australian Federal Police estimated the group had enabled the theft of more than 500,000 credentials across more than a thousand organizations.

The tooling is public. In May 2026, TeamPCP released the worm's source code and co-launched a $1,000 BreachForums contest scored by the download counts of compromised packages. By mid-2026, multiple operators were running Shai-Hulud-style campaigns from the same code. A technique that once required a capable team now requires a forum account.

The attack surface moved into CI. Trusted publishing replaced long-lived npm tokens with short-lived OIDC credentials minted inside CI. Attackers followed the credential. The Mini Shai-Hulud wave that hit TanStack in May 2026 stole no long-lived npm token: attacker code ran inside TanStack's GitHub Actions workflow, read an OIDC token from runner memory, and published with valid Sigstore provenance. As Socket's TanStack analysis put it, "an attacker who can execute in GitHub Actions can generate valid Sigstore attestations for malicious packages."

AI agents add a fifth pressure. Coding agents install packages without a human reading the name. In September 2026, the UK AI Security Institute reported that GPT-6 Astra reached the payload-delivery stage of a supply chain attack in 29.2% of simulated capture-the-flag runs with OpenAI's cyber classifiers disabled. It had not been asked to attack open source.

We do not quote a headline count of attacks per year. Published tallies count packages, versions, or incidents, and are not comparable with each other. The defensible measure of the trend is the one above: the cost of an attack has fallen while its yield has risen. The OWASP Top 10 2025 reflects the same shift: it added Software Supply Chain Failures as a new category, ranked third.

How do you prevent a supply chain attack?

#

You prevent a supply chain attack by breaking it at each of its three stages separately, because no single control touches all three. Every control below stops some rows of the incident table and misses others.

ControlStageLikely stopsMissesCost
Phishing-resistant 2FA, no long-lived publish tokensEntryCredential phishing like Qix; stolen tokens like Shai-HuludCode running inside a trusted CI workflow (TanStack); leaked org keys (LiteLLM)Migrating release automation
Least-privilege CI, no pull_request_target on untrusted codeEntryThe TanStack chainA compromised maintainer with legitimate accessWorkflow rewrites
Provenance policy pinned to the expected source repositoryEntryLiteLLM, whose provenance recorded Berri-AI/litellmMalicious code built by the real pipeline (TanStack)Policy upkeep per package
Lockfile and npm ciReachExisting installs during the axios windowNew dependencies, upgrades, npx, global installsLow
Release-age delay of 24 hours or moreReachchalk, Shai-Hulud, axios, LiteLLM, all reported within hoursSlow attacks: xz (34 days), event-stream, SolarWindsUrgent fixes wait unless exempted
Pin CI actions to full commit SHAsReachtj-actions tag repointingA pinned commit that is itself maliciousManual or automated bumps
No dependency install scripts by defaultTriggerua-parser-js, Shai-Hulud, axios, TanStack's git prepare, Phantom GypRuntime payloads (Qix), Python startup hooks (LiteLLM), CI actionsNative modules need explicit approval
CVE and advisory scanningAfter the factNone of these before executionMalware, until someone writes an advisoryLow
Behavior analysis of each artifact at fetch timeReach and triggerLoud payloads within minutes of publishQuiet, targeted code; packages already in a local cacheA proxy or wrapper in the install path

Four of these deserve detail.

A lockfile freezes what you already resolved. It does nothing for the next resolution, and checking one after an incident proves little. In the words of our axios follow-up, "Lockfiles make existing installs deterministic. They do not make new installs safe." Code fetched by npx or a global install never appears in a project lockfile at all.

A dependency cooldown refuses versions younger than a threshold. pnpm 11 defaults to 24 hours, npm added a minimum release age setting in its 11.x line, and Rust 1.100 is scheduled to add an opt-in Cargo setting on November 12, 2026. Every npm and PyPI attack in Figure 2 was reported or replaced within about four hours, long before a one-day delay would have let it install. A cooldown is useless against xz-style patience, and it delays security fixes unless you exempt them.

Package provenance proves which pipeline built an artifact, not that the pipeline was honest. Provenance and signatures are necessary but not sufficient. They still pay off when a policy checks them: the LiteLLM image's provenance recorded the lookalike repository Berri-AI/litellm, which a rule pinning the expected source would have rejected.

Behavior analysis reads what the artifact does instead of what its metadata claims. Socket Firewall applies it at fetch time: sfw wraps npm, pnpm, yarn, pip, uv, or cargo, filters their registry requests, and blocks package artifacts Socket has confirmed as malware. Its free tier warns on, but does not block, versions flagged by AI and not yet confirmed by a human, and it never sees packages served from a local cache.

Our verdict: consumers should run npm 12 or pnpm 10 and later, install from a committed lockfile with npm ci, set a release-age delay of at least 24 hours, pin CI actions to SHAs, and inspect everything a lockfile does not cover at fetch time. Publishers should remove long-lived tokens, require a human 2FA approval for each release, as npm's staged publishing does, and treat CI secrets as production credentials. None of this stops an attacker who already owns the supplier's organization.

The release is the last step of the attack

#

The LiteLLM compromise shows where the remaining gap is. The malicious release was caught in minutes. The exposure that produced it lasted months: a leaked key sat unrevoked from May 2026, and attacker activity is documented from October 10.

Inside the supplierat least 11 days of undetected activity

  1. May 2026An Agent Platform master key is committed to a public repository and never revoked.
  2. Oct 10, 22:11 UTCA push to throwaway branch ci-tmp-a351cc runs cache.yml, which posts 6 org secrets, including GH_TOKEN, to a webhook.site collector.
  3. Oct 12, 12:16 UTCA six-job version targets 14 more secrets across five GitHub Environments. Each branch is deleted the day it is created.
  4. Oct 21, 10:51 UTCThe malicious release build starts. Its provenance records the source Berri-AI/litellm, one hyphen away from BerriAI/litellm. LiteLLM's audit logs tie the release approval to the token stolen on Oct 10.

On the registry7 minutes to an alert

  1. Oct 21, 11:39:51 UTCSeven litellm==1.107.0 wheels are published to PyPI.
  2. Oct 21, 11:46:30 UTCSocket's AI scanner raises malware alerts on all seven.
  3. Oct 21, 13:41 UTCSocket publishes its analysis of the startup-hook stealer.
Figure 3. The LiteLLM compromise, from LiteLLM's attack-chain investigation and Socket's two reports. The public release was the last step, not the first.

Figure 3 lays out the sequence from LiteLLM's investigation and our follow-up. In May 2026, an Agent Platform master key was committed to a public repository and never revoked. On October 10 at 22:11 UTC, a push to a throwaway branch named ci-tmp-a351cc ran a workflow called cache.yml that posted six organization secrets, including GH_TOKEN, to a webhook.site collector. On October 12 at 12:16 UTC, a six-job version targeted 14 more secrets across five GitHub Environments. Each branch was deleted the day it was created. On October 21 at 10:51 UTC, the malicious release build started; its provenance recorded the source Berri-AI/litellm, and LiteLLM's audit logs tie the release approval to the token stolen on October 10. The seven 1.107.0 wheels reached PyPI at 11:39:51 UTC. Socket's AI scanner raised malware alerts at 11:46:30 UTC, seven minutes later, and Socket published its analysis at 13:41 UTC.

Registry-side detection now runs in minutes. Inside LiteLLM's own organization, the attacker worked undetected for at least 11 days, using access that traced back to a key exposed since May, and chose when to cash in. A push that runs a workflow on a branch deleted minutes later looks like routine CI to most organizations.

The next gain in supply chain security will not come from scanning releases faster. It will come from suppliers treating their CI secrets, org tokens, and release approvals with the audit trails and anomaly detection they already apply to production, because for everyone downstream, that is what they are.