Key takeaways
- In 2025 and 2026 most large supply chain attacks hijacked real packages instead of publishing fake ones: through a maintainer's infected PC (axios, 31 March 2026), a publish credential LiteLLM believes was stolen through its CI (24 March 2026) or the project's own release pipeline (TanStack, 11 May 2026).
- Hijacked versions of popular packages were usually live for hours: about 40 minutes for LiteLLM 1.82.x, about 3 hours for axios, and 1 hour 43 minutes for LiteLLM 1.107.0 on 21 October 2026.
- Valid provenance proves where a package was built, not that the build was honest: AsyncAPI (July 2026) and keyv in the ChainDrop worm (August 2026) shipped malware from their own release workflows with valid provenance.
- Disabling install scripts is necessary but not sufficient: AsyncAPI's July 2026 payload ran on import, and LiteLLM's .pth files ran on every Python start.
- A three-day release cooldown (npm min-release-age, pnpm minimumReleaseAge, uv exclude-newer, Dependabot cooldown) skips hijacked releases that are pulled within a day, but not moved Git tags like tj-actions (2025) or long cons like xz (2024).
What changed recently (1 November 2026): added the second LiteLLM compromise (21 October), the tensorlake release whose payload wipes your home directory when you revoke its stolen token (8 October), and the trojanized Claude Code extension on Cursor's marketplace (29 October).
A working reference, updated when a major incident lands. Every incident and number links to a source.
What is a software supply chain attack?
A software supply chain attack compromises something developers already trust, such as an open-source package, a CI action, a build pipeline or a vendor update, so malicious code reaches every downstream user without attacking them directly. ENISA calls it "a combination of at least two attacks": one on a supplier, then one on the target through it.1 NIST: "an incident where an adversary exploits vulnerabilities in the product or service supply network of the intended target."2
How it works, in three steps
- Compromise something upstream: a maintainer's account or laptop, a CI workflow, a publish token, a build server, or an unregistered package name.
- Poison an artifact people already pull: a new package version, a GitHub Action tag moved to another commit, a rebuilt image, an extension update.
- Let inherited trust run it. Version ranges,
latesttags, auto-updates and CI install it without a human looking, with the permissions of a developer laptop or a build job that holds publish tokens and cloud keys.
How it differs from a vulnerability
A vulnerability is an accidental flaw in legitimate code. A supply chain attack delivers deliberately malicious code through a trusted channel, so you remove the version, treat every machine that ran it as compromised and rotate what it could read. MOVEit Transfer (2023) is often called a supply chain attack, but Cl0p exploited a zero-day (CVE-2023-34362) in widely used file-transfer software, and no supplier's code was altered.3 It was a supply chain problem only for organizations whose vendors ran MOVEit.
The term also covers commercial software updates: SolarWinds Orion (2020) shipped a digitally signed component carrying a backdoor and 3CX (2023) shipped trojanized installers,4,5 and in 2025 Notepad++'s update traffic was hijacked through its hosting provider.6 This post focuses on open-source packages and CI, where engineering teams can act directly.
Supply chain attacks by the numbers (2025 to 2026)
| Measure | Figure | Source and caveat |
|---|---|---|
| New malicious open-source packages identified in 2025 | More than 454,600; over 1.233 million known in total; over 99% on npm | Sonatype, 2026 edition.7 Counts packages, and bulk spam floods inflate it. |
| New malicious packages, Q3 2026 | 149,329 (npm 89.5%) | Sonatype Q3 2026 Open Source Malware Index.8 |
| Change in malicious-package detections, 2025 | +73%; npm nearly 90% of detections; PyPI down 43% | ReversingLabs 2026 report.9 Counts detections, so do not compare it with Sonatype's package counts. |
| Breaches with third-party involvement | 48% of breaches | Verizon DBIR 2026, up 60% on the previous dataset.10 It counts any third-party involvement, not malicious packages. |
| How long hijacked versions of popular packages stayed live | About 40 minutes to 12 hours | Our synthesis of the timeline below (LiteLLM, axios, TanStack, Trivy, crates.io arrayref). |
| How long low-profile malware stayed live | Weeks to years | xz (about 5 weeks), faster_log on crates.io (about 4 months),11 a Go typosquat of shopspring/decimal (since 2023).12 |
Two warnings. Vendor counts measure different things (packages, versions, detections) over different windows, even across editions of the same report, so do not compute growth rates from them. And the often-quoted "45% of organizations will have experienced attacks on their software supply chains by 2025" is a Gartner prediction from March 2022, not a measurement.13
For defense, what matters most is how long a hijacked version stays installable. pnpm's maintainers, shipping their release-age setting: "In most cases, such attacks are discovered quickly and the malicious versions are removed from the registry within an hour."14 The timeline below mostly agrees.
Timeline: major software supply chain attacks, 2024 to 2026
Times are UTC. "Where it ran" is the point at which the malicious code executed on victims: at install, at import or interpreter start, inside CI, or at runtime in the application.
| Date | Ecosystem | Incident | Entry point | Where it ran | Live |
|---|---|---|---|---|---|
| 2024-02-24 to 03-29 | Linux (C library) | xz-utils 5.6.0 and 5.6.1 backdoor, CVE-2024-309415,16 | Multi-year social engineering to become maintainer; payload hidden in Git test files, activated by a build step only in the release tarballs | Runtime (sshd via liblzma) | About 5 weeks; caught before most stable distros shipped it |
| 2024-12-03 | npm | @solana/web3.js 1.95.6 and 1.95.717 | Compromised publish-access account | Runtime (private key theft) | About 5 h (15:20 to 20:25) |
| 2024-12-04 to 07 | PyPI | ultralytics 8.3.41, 8.3.42, 8.3.45, 8.3.4618,19 | Branch-name injection in a pull_request_target workflow plus Actions cache poisoning; then a leftover API token | Runtime (cryptominer) | Hours per version; two rounds over 4 days |
| 2025-03-14 to 15 | GitHub Actions | tj-actions/changed-files, CVE-2025-30066, via reviewdog/action-setup20,21 | Chain of stolen personal access tokens; every tag repointed | CI (secrets printed to logs) | About 1 day; "over 23,000" repos used it |
| 2025-08-26 to 27 | npm | Nx "s1ngularity"22,23 | PR-title injection in a pull_request_target workflow leaked the npm token | Install (postinstall) | About 4 h for nx; plugins up to about 12 h |
| 2025-09-08 | npm | chalk, debug and 16 more packages (maintainer Qix)24,25 | Phishing email from npmjs.help posing as a 2FA reset | Runtime in browsers (crypto address swapper) | Hours |
| 2025-09-14 to 16 | npm | Shai-Hulud, the first self-replicating npm worm per ReversingLabs26,27,28 | Stolen npm tokens republish the victim's packages | Install (postinstall) | Days; 500+ packages removed |
| 2025-11-24 | npm | Shai-Hulud 2.0, "Sha1-Hulud: The Second Coming"29,30 | Worm, stolen tokens | Install (preinstall, via Bun) | Days; about 700 to 800 packages |
| 2026-02-17 | npm | cline 2.3.031 | Prompt injection in an AI issue-triage workflow, cache poisoning, then a token that was not rotated | Install (postinstall installed another tool globally) | About 8 h |
| 2026-03-19 to 23 | GitHub Actions, binaries, containers | Trivy v0.69.4, trivy-action, setup-trivy, CVE-2026-3363432 | Credentials kept from an earlier breach because rotation "was not atomic"; tags force-pushed | CI (runner memory dumped) | Binary about 3 h; action tags about 12 h |
| 2026-03-24 | PyPI | litellm 1.82.7 and 1.82.833 | PyPI credential believed stolen through Trivy in LiteLLM's CI | Payload in proxy_server.py; 1.82.8 also at every Python start (.pth)34 | About 40 min (LiteLLM's figure) |
| 2026-03-31 | npm | axios 1.14.1 and 0.30.435 | Remote access trojan on the lead maintainer's PC, via social engineering | Install (postinstall of an added dependency) | About 3 h |
| 2026-05-11 to 12 | npm, PyPI | 84 versions of 42 @tanstack packages; over 170 packages in the wave (OX Security)36,37 | pull_request_target, cache poisoning, OIDC token read from runner memory | Install (prepare script); PyPI variants on import38 | About 1 h 43 min to full deprecation |
| 2026-05-18 | VS Code, Open VSX | Nx Console 18.95.0, leading to a breach of GitHub's internal repositories39,40 | Contributor's GitHub CLI token stolen by the TanStack wave | Editor (extension auto-update) | 17 min (Marketplace), 36 min (Open VSX) |
| 2026-05-22 | Packagist | Laravel-Lang packages: 233 versions41,42 | New tags in the official repos pointed at commits in an attacker's fork | Composer autoload | Not stated |
| 2026-06-17 | npm | Mastra: 140+ @mastra packages43,44 | Former contributor's npm account whose publish rights were never revoked | Install (postinstall of an added typosquat) | Hours; clean versions the same day |
| 2026-07-14 | npm | @asyncapi/generator, -helpers, -components, specs45 | A pull_request_target workflow ran pull-request code, which Microsoft believes exposed a bot token; the real release workflows published | Import (require) | Not stated |
| 2026-08-04 | npm | "ChainDrop": keyv, cacheable, flat-cache and 400+ more46,47,48 | keyv maintainer's GitHub account; the project's own release workflow; then a worm | Install (preinstall) | Not stated |
| 2026-08-20 | crates.io | arrayref 0.3.10, internment 0.8.7, append-only-vec 0.1.949 | Author's computer or credentials likely compromised; new dependency on the malicious lookalike proc-macro1 | Build (build.rs) | 86 to 107 min |
| 2026-10-08 | npm | tensorlake 0.5.14450 | Commits pushed straight to main; the repo's release workflow published | Install (preinstall); wiper on token revocation | Not stated |
| 2026-10-21 | PyPI, Docker Hub, GHCR | litellm 1.107.051,52 | GitHub CLI tokens of an organization owner and two maintainers; release workflow pointed at a lookalike repo | Every Python start (.pth, sitecustomize.py) | About 1 h 43 min on PyPI, until quarantine |
| 2026-10-29 | Cursor marketplace | Trojanized copy of the Claude Code extension, builds 2.1.312 and 2.1.31353,54 | Access to Cursor's marketplace used to publish a tampered copy | Editor (SSH key planted, reverse tunnel) | About 11 h (09:27 to 20:35) |
The pattern across the table: in 2025 and 2026 most large attacks did not rely on fake packages. They hijacked real ones, through a maintainer account or machine (axios, chalk, Mastra), a CI token (LiteLLM, Trivy), or the project's own release pipeline (TanStack, AsyncAPI, ChainDrop, tensorlake).
xz-utils backdoor (March 2024)
A contributor using the name Jia Tan joined xz in late 2021, was a maintainer within about 14 months, and in February and March 2024 released 5.6.0 and 5.6.1 with a backdoor in liblzma that let the key holder run commands through sshd on some Linux distributions.16 The payload sat in test files committed to Git; the build step that activated it existed only in the release tarballs. Andres Freund noticed slow SSH logins on Debian unstable and reported it on 29 March 2024, about five weeks after the first backdoored release and before most stable distributions shipped it.15 A cooldown does nothing against a patient insider; comparing published tarballs with the repository does.
tj-actions/changed-files (March 2025)
The chain started with a token leaked from the spotbugs project in December 2024, moved to reviewdog/action-setup on 11 March 2025, and reached tj-actions/changed-files on 14 March, when every tag of the action was repointed to a malicious commit.21 The payload read secrets from the runner process's memory and printed them, double base64-encoded, into the workflow logs, which anyone can read on a public repository. GitHub's advisory counts "over 23,000" repositories using the action.20 Unit 42 says the attack initially targeted Coinbase's agentkit project. StepSecurity spotted it from an unexpected outbound connection on a monitored runner.55 A tag is a pointer anyone with write access can move; a full commit SHA is not.
Worms: Shai-Hulud, Shai-Hulud 2.0 and ChainDrop (2025 to 2026)
Shai-Hulud (September 2025) was, per ReversingLabs, the first self-replicating npm worm. A postinstall script ran the TruffleHog secret scanner,26 pushed what it found to a public GitHub repository named "Shai-Hulud", and used stolen npm tokens to publish infected versions of the victim's other packages. GitHub removed more than 500 packages.27,28 Shai-Hulud 2.0 (November 2025) moved to a preinstall script that installed Bun and ran an obfuscated payload; Koi Security found it tries to destroy the home directory when it can neither steal a GitHub or npm token nor open an exfiltration channel.30 Wiz counted about 700 to 800 packages and over 25,000 repositories.29
The biggest 2026 wave was ChainDrop on 4 August. An attacker with the keyv maintainer's GitHub account pushed commits, and at 09:35 UTC the project's real release workflow published keyv 6.0.0 with genuine SLSA provenance.46,47 A preinstall setup.mjs started the payload under Bun, which also committed .claude/settings.json SessionStart hooks and .vscode/tasks.json tasks to repository branches, so opening the project in Claude Code or VS Code runs it again.46 Microsoft counts "more than 400 packages";48 Aikido first posted 868, corrected it to 434 the same day and to 444 on 5 August, so both 868 and 444 circulate.56,47
Elastic found the worm spreads only with npm tokens that have write permission and bypass 2FA.57 npm restricted those tokens for account and package management on 31 July 2026, but they can still publish until npm's January 2027 target.58 Microsoft notes the payload "contains a destructive handler if the monitored token is revoked".48 The May TanStack wave had already used one.37 In October, tensorlake (same setup.mjs and Math_Symbol.js loader as ChainDrop) carried a monitor that checks the stolen GitHub token every 60 seconds for up to 24 hours and runs rm -rf ~/ once GitHub rejects it.50,46 Full playbook: protecting against Shai-Hulud-style npm worms.
Trivy, LiteLLM and the TeamPCP cascade (March 2026), and LiteLLM again (October 2026)
In late February 2026 an automated account exploited a pull_request_target workflow in Trivy's repository and stole a token.59 Aqua's rotation after the 1 March disclosure "was not atomic", so the attacker kept access. On 19 March the attacker published trivy v0.69.4 through the real release pipeline and force-pushed 76 of 77 tags of aquasecurity/trivy-action and all 7 of setup-trivy to commits that dumped the CI runner's memory for secrets.32 The attacker calls itself TeamPCP, per Wiz.60 The campaign continued: CanisterWorm on npm from 20 March, which Aikido calls "a direct follow-up from the attack on Trivy" (its initial list named 47 packages);61 Checkmarx's GitHub Actions and Open VSX extensions on 23 March, through access Checkmarx traces to the Trivy attack;62 LiteLLM on 24 March; and the Telnyx SDK on 27 March, published with a PyPI token Endor Labs believes the LiteLLM payload harvested.63
LiteLLM believes its compromise "originated from the Trivy dependency" in its CI. Versions 1.82.7 and 1.82.8 appear to have been uploaded straight to PyPI, bypassing CI, and were live "for about 40 minutes" from 10:39 UTC before quarantine; other published timelines are wider.33 1.82.8 added litellm_init.pth, and Python runs import lines in .pth files at every interpreter start, "regardless of whether a particular module is actually going to be used".34 It was found because the file caused a fork bomb on a FutureSearch developer's machine, where litellm was a transitive dependency of an MCP plugin.64
On 21 October LiteLLM was compromised again. The attacker held GitHub CLI tokens of an organization owner and two maintainers, used the owner's to override branch protection, and pointed the release workflow at a lookalike repository, Berri-AI/litellm. Seven malicious 1.107.0 wheels reached PyPI at 11:39 to 11:40 UTC, and PyPI quarantined the release at 13:23.52 The payload (cook.pth, sitecustomize.py) runs on every Python start, and the malicious Docker images carry a valid signature from LiteLLM's previous cosign key. LiteLLM recommends pinning 1.106.2.51
axios (31 March 2026)
axios 1.14.1 was published at 00:21 UTC and 0.30.4 around 01:00; both were removed at 03:15, "about 3 hours" later.35 The axios source was unchanged. The only difference was a new dependency, plain-crypto-js@^4.2.1, whose postinstall script installed a remote access trojan on macOS, Windows and Linux. A clean 4.2.0, published about 18 hours earlier, apparently gave the name some history; Socket's automated detection flagged 4.2.1 about six minutes after it appeared.35,65 The post-mortem says to downgrade to 1.14.0 (or 0.30.3).35
The lead maintainer's post-mortem says the attacker reached the maintainer's PC "through a targeted social engineering campaign and RAT malware" two weeks earlier: a cloned company founder, a Slack workspace, then a Teams call in which the maintainer was told something on the system was out of date. 2FA was enabled; with a trojan on the machine it did not matter.35 The malicious versions came from the maintainer's personal account; the post-mortem says the OIDC publishing flow the project is now adopting "should have been in place before this happened".35,65 Google attributes the attack to UNC1069, a North Korea-nexus group, and Microsoft to Sapphire Sleet, which it calls a North Korean state actor; Google counts over 100 million weekly downloads for axios 1.x, Microsoft over 70 million.66,67 In June npm added a 72-hour read-only hold for high-impact accounts that change their email or use a recovery code, citing the pattern in which "a compromised account changes its email, mints a new token, and publishes malicious versions".68
TanStack (11 May 2026)
A fork's pull request ran in a pull_request_target bundle-size workflow and saved a poisoned pnpm store into the Actions cache under a key the release workflow would restore. When that workflow ran, attacker code read its OIDC token from the runner's memory and published 84 versions of 42 packages at 19:20 and 19:26 UTC. TanStack: "No npm tokens were stolen and the npm publish workflow itself was not compromised." The publishes went through TanStack's own trusted-publisher binding, and the versions carried valid SLSA provenance.36,69 The payload ran from the prepare script of a Git dependency added through optionalDependencies; it was public within 20 to 26 minutes and fully deprecated at 21:03.36 The wave reached over 170 npm and PyPI packages, according to OX Security; on PyPI, mistralai 2.4.6 ran its code on import, and guardrails-ai 0.10.1 carried the same malware.38,37 The npm payload also installed a gh-token-monitor that checks a stolen GitHub token every 60 seconds for up to 24 hours and tries to run rm -rf ~/ if the token is revoked.70,37 Wiz attributes the wave to TeamPCP "with high confidence".70
The damage travelled. A Nx contributor installed a malicious @tanstack package because the pinned pnpm 10.14 silently ignored the project's 7-day release-age setting; the malware used the contributor's GitHub CLI token "within 74 seconds", and a malicious Nx Console extension shipped on 18 May.39 GitHub later said an employee's device was compromised through a poisoned VS Code extension, and that the attacker's claim of about 3,800 exfiltrated internal repositories was "directionally consistent" with its investigation.71,40
Before 2024: the landmark cases
event-stream (npm, 2018): the author handed the module to a volunteer who asked to maintain it, and the volunteer added a dependency, flatmap-stream, whose payload only activated inside the Copay bitcoin wallet's build.72,73 SolarWinds (2020): a build-time implant put the SUNBURST backdoor into a signed Orion component; "fewer than 18,000" customers may have installed it, and the White House counted 9 federal agencies and about 100 companies compromised.74,4,75,76 Codecov (2021): its Bash Uploader script, altered periodically from 31 January, exposed customers' CI environment data to an attacker until a customer noticed a checksum mismatch on 1 April.77 3CX (2023): a trojanized X_TRADER installer, downloaded from Trading Technologies' own website onto an employee's personal computer, led to trojanized 3CX installers,78 "the first time Mandiant has seen a software supply chain attack lead to another software supply chain attack".5
Types of software supply chain attacks
| Type | How it works | Recent example | More |
|---|---|---|---|
| Account or token takeover | A maintainer's session, password and one-time code, or publish token is used to release a new version | axios (trojan on the maintainer's PC, 2026), chalk and debug (fake 2FA-reset email, 2025), Mastra (stale contributor account, 2026) | Account takeover |
| CI/CD pipeline compromise | Untrusted pull requests run in privileged workflows, poison caches or steal OIDC tokens; the real pipeline publishes | Ultralytics (2024), Nx (2025), TanStack and AsyncAPI (2026) | Hardening CI |
| Moved tags | Existing Git tags are force-pushed to malicious commits, so references that look pinned change | tj-actions (2025), trivy-action (2026) | |
| Self-propagating worm | Stolen tokens republish every package the victim can publish | Shai-Hulud (2025), ChainDrop (2026) | Worm protection |
| Typosquatting | A name one character or separator away from a popular package | proc-macro1, a lookalike of proc-macro2 on crates.io (2026), easy-day-js imitating dayjs in the Mastra attack (2026) | Typosquatting |
| Slopsquatting | Registering names AI assistants hallucinate: at least 5.2% of packages suggested by commercial models, and 21.7% by open-source ones, did not exist in one study79 | In an Anthropic evaluation, an AI agent published malware under a non-existent PyPI name it found in a document; 15 real systems ran it (reported July 2026)80 | Slopsquatting |
| Dependency confusion | A public package with the name of an internal one wins dependency resolution | PyTorch nightly's torchtriton (December 2022)81 | Dependency confusion |
| Malicious insider or protestware | A trusted maintainer adds the payload | xz (2024); node-ipc overwrote files on Russian and Belarusian machines (2022)82 | Protestware |
| Vendor update hijack | A vendor's build system or update channel ships the payload, often signed | SolarWinds (2020), 3CX (2023), Notepad++ (2025) | |
| Third-party web script | A script or CDN that many sites embed changes hands or is altered | polyfill.io, embedded by 100K+ sites per Sansec (2024)83 | |
| Editor extensions and AI tooling | Extensions, agent skills and coding-tool config files run code on developer machines | Nx Console (2026), the Claude Code copy on Cursor's marketplace (2026), ChainDrop's .claude/settings.json hooks |
Where malicious code runs: npm, PyPI, crates.io, Go, RubyGems, Packagist
Which defenses work depends on when a package's code first runs on your machine, and that differs by registry.
| Ecosystem | Runs at install or build | Runs at import or start | Recent incident | Native controls |
|---|---|---|---|---|
| npm | preinstall, install, postinstall; prepare for Git dependencies; implicit node-gyp builds from binding.gyp | Any top-level code on require or import | axios (postinstall), AsyncAPI (import) | npm 12: dependency scripts off unless approved, Git and URL dependencies off by default;84 min-release-age;85 trusted publishing with provenance;86 staged publishing;87 publish-time malware scan88 |
| PyPI | Building a source distribution runs its setup.py or build backend; installing a wheel runs nothing | import lines in .pth files at every interpreter start; top-level code on import | LiteLLM 1.82.8 and 1.107.0 (.pth), Telnyx (import) | Trusted Publishing and attestations;89 releases reject new files after 14 days;90 pip --uploaded-prior-to and --only-binary :all:; uv exclude-newer |
| crates.io | build.rs scripts and procedural macros run at cargo build, and often when an editor's language server analyzes the project | Only when called, unless a crate registers pre-main constructors (for example with ctor) | arrayref, internment, append-only-vec via proc-macro1 (2026) | Cargo.lock; trusted publishing91 and a "Trusted Publishing only" mode per crate92 |
| Go | No install hooks | init() functions when the program or its tests start | shopsprint/decimal, a typosquat whose init() polled DNS for commands, in the module proxy since 202312 | go.sum and the checksum database make versions immutable; the module proxy keeps serving cached versions after a repo is deleted |
| RubyGems | Native extensions (extconf.rb) at gem install; rubygems_plugin files load on later gem commands93 | Top-level code on require | SleeperGem: dormant gems suddenly shipped malicious releases with no matching source commit (July 2026);94 500+ malicious gems yanked and sign-ups paused (May 2026)95 | Gemfile.lock; trusted publishing96 |
| Packagist | Only the root project's scripts run, never a dependency's;97 Composer plugins can, and need allow-plugins98 | Autoloaded files on every request | Laravel-Lang: new tags pointing at an attacker's fork, payload at autoload (2026)41 | Immutable stable versions (announced late May 2026);42 Composer 2.10 blocks known malware at install and update and fails composer audit on it by default99 |
npm is still the main target (over 99% of open-source malware in Sonatype's 2025 data); the npm supply chain attacks guide covers npm-specific attack types and defenses in depth.
Why provenance, 2FA and --ignore-scripts were not enough in 2026
Provenance proves where a package was built, not that the build was honest
Several 2026 malicious releases came from the victim's own pipeline, so their provenance was real. Microsoft on AsyncAPI: "All five malicious versions were published through npm trusted publishing using GitHub OIDC and carried valid provenance attestations."45 Datadog on keyv 6.0.0: the provenance was "neither forged nor a safety verdict".46 TanStack's and tensorlake's malicious releases also came from their own release workflows,36,50 and LiteLLM 1.107.0's images carried the project's own signature.51 Provenance still helps as a signal the other way: a release that suddenly lacks the provenance its predecessors carried deserves a look, and pnpm's trustPolicy: no-downgrade fails an install when a package's trust level drops.100
2FA stops phishing, not a compromised machine or a bypass token
In 2025 lookalike domains phished maintainers: a fake 2FA-reset email from npmjs.help for chalk and debug, pypj.org on PyPI.25,101 Phishing-resistant passkeys defeat that, and npm has been phasing out TOTP in their favor since September 2025.102 They do not help when the attacker controls the maintainer's machine (axios) or holds a 2FA-bypass token, which the worms spread with. After the @antv wave in May 2026, Microsoft reported that GitHub invalidated 61,274 npm granular tokens with write access and 2FA bypass.103
Disabling install scripts removes one entry point, not all of them
Install hooks are still the most common entry point, and npm 12 blocks dependency lifecycle scripts and implicit node-gyp builds unless approved.84 But AsyncAPI's payload "executes at module-load (import/require) time", and Microsoft says plainly not to rely on --ignore-scripts.45 Python .pth files run at interpreter start (LiteLLM), Composer autoload on every request (Laravel-Lang) and Go init() at program start. For code that runs on import, the only checks are reading it or not installing that version yet.
A cooldown only works if your tools apply it
The Nx contributor had a 7-day release-age setting, but "pnpm 10.14 silently ignores the unknown config key".39 pnpm 12 now reports unrecognized settings.104 Cooldowns also do nothing for moved tags (tj-actions, trivy-action), since no new version is released; SHA pinning covers that.
How to prevent software supply chain attacks
Teams that install dependencies
- Install exactly what the lockfile says. Commit lockfiles and install with
npm ci,pnpm install --frozen-lockfileor the equivalent. CISA's May 2026 alert: "Pin software to specific trusted versions."40 - Wait before adopting new releases. CISA says to "wait at least three hours before pulling a new package";40 three days covers every hijacked release in the timeline above that was pulled within a day. The settings, by tool:
# .npmrc (npm 11.10.0 or later; the value is in days) min-release-age=3 # pnpm-workspace.yaml (the value is in minutes; pnpm 11 defaults to 1440) minimumReleaseAge: 4320 trustPolicy: no-downgrade # pyproject.toml (uv) [tool.uv] exclude-newer = "P3D" # pip pip install --uploaded-prior-to P3D -r requirements.txt
Units differ: days for npm,85 minutes for pnpm (default 1440),100,105 ISO 8601 durations for uv106 and pip (pip needs upload times from the index).107 Bun takes seconds (minimumReleaseAge = 259200under[install]is 3 days),108 and Yarn 4.10+ hasnpmMinimalAgeGate.109 Then confirm your installed version honors the setting. - Delay update PRs too. Dependabot's
cooldownand Renovate'sminimumReleaseAge:110,111# .github/dependabot.yml version: 2 updates: - package-ecosystem: "npm" directory: "/" schedule: interval: "weekly" cooldown: default-days: 3 // renovate.json { "minimumReleaseAge": "3 days" } - Allow-list install scripts. npm 12 runs no dependency install scripts by default:
npm approve-scripts --allow-scripts-pendinglists what is waiting, andnpm approve-scripts <pkg>adds a version-pinned entry to theallowScriptsfield ofpackage.json, which you commit.84,112 On older npm, setignore-scripts=true(details). pnpm 11 runs no dependency build scripts unless listed inallowBuilds.105 - Keep Git and URL dependencies off. npm 12 defaults
--allow-gitand--allow-remotetonone;84 pnpm 11 turns onblockExoticSubdepsby default.105 - Know what you run. Keep an SBOM or searchable lockfiles so "are we affected?" takes minutes, turn on Dependabot malware alerts for npm,113 and review the code of new and changed dependencies.
CI pipelines
- Pin actions to full commit SHAs, and turn on the GitHub policy that fails workflows using unpinned actions.114 Pins are not transitive: trivy-action pins older than April 2025 still pulled the malicious setup-trivy.32
permissions: contents: read jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@<full 40-character commit SHA> # vX.Y.Z with: persist-credentials: false - run: npm ci - Never run pull-request code in a privileged context. GitHub has narrowed
pull_request_target: it always uses the default branch's workflow,115actions/checkoutrefuses common pwn-request patterns,116 and untrusted triggers get a read-only cache.117 The checkout guard does not coverrun:steps that fetch PR code with git or gh, or triggers such asissue_comment, and it can be switched off. Do not give AI agents in workflows shell access on anyone's input (Cline).31 - Keep secrets away from installs. No publish tokens or cloud keys in the install job, minimal
permissions, no cache restores in release workflows. - Watch egress. tj-actions was caught from an unexpected outbound connection on a runner.55
- Rotate atomically and inventory every token: Trivy's rotation was "not atomic", and Cline deleted the wrong token.32,31
Maintainers who publish packages
- Publish with trusted publishing (OIDC) on npm, PyPI, crates.io and RubyGems, and delete long-lived tokens; a leftover token gave Ultralytics its second round.18 Scope the publisher to one workflow and a protected environment with required reviewers: SAP's
@cap-jspackages were published because the configuration covered the whole repository.118 npm now rejects trusted-publishing tokens minted fromissue_commentevents.119 - Require a human approval to release. npm's staged publishing holds a version until a maintainer approves it with 2FA.87
- Use passkeys, and treat GitHub CLI tokens on laptops as publish credentials: they were the way in for Nx Console and LiteLLM in October.39,51
- Remove people who no longer maintain the package. Mastra's attacker used a former contributor's account.44
- Protect release branches and use immutable releases, which do not freeze existing Action tags.120,32
What to do if you installed a compromised version
- Confirm exposure. Search lockfiles (and their history), images and CI caches for the exact versions, and check the vendor's indicators. For LiteLLM 1.107.0, LiteLLM lists
cook.pth,sitecustomize.pyand a lock file at/tmp/remote_code_loader.lock, and warns that not finding them "does not prove the environment is clean".51 - Isolate the machine or runner first. Take it off the network before revoking anything, and keep it off until step 3 is done: someone else may revoke the token meanwhile, and the wiper fires on its next check.
- Disarm persistence before you revoke anything. For the tensorlake and ChainDrop family, StepSecurity's advice is "Don't revoke any GitHub token" until the token monitor is stopped and deleted, because revocation triggers
rm -rf ~/.50,48 It runs as the systemd user servicegh-token-monitor.serviceon Linux, the LaunchAgent~/Library/LaunchAgents/com.user.gh-token-monitor.pliston macOS, or a logon task runningmonitor.ps1on Windows. Stop it, then delete~/.config/gh-token-monitor,~/.local/bin/gh-token-monitor.shand the service file or LaunchAgent plist.50 Also look for.claude/settings.jsonand.vscode/tasks.jsonhooks committed to your repositories.46 - Rotate everything the machine or job could read, from a clean machine: npm, PyPI and GitHub tokens (including GitHub CLI logins), cloud and Kubernetes credentials, SSH keys and AI API keys.48,40,57
- Purge caches and rebuild. Clear package-manager caches (Microsoft names npm and Yarn; do the same for pnpm and pip), shared CI caches, base images and runner images.48
- Hunt across your GitHub organization for public repositories you did not create (descriptions such as "Sha1-Hulud: The Second Coming" or "Shai-Hulud: Here We Go Again"), self-hosted runners named SHA1HULUD, unfamiliar workflow files such as
.github/workflows/discussion.yaml, and commits byclaude@users.noreply.github.comtitled "chore: update dependencies".29,30,50 - Pin the last good version, and reinstall the machine if you cannot be sure it is clean.50
Where source-level review fits
Advisory tools (npm audit, Dependabot, OSV) only know a malicious version after someone reports it; reading the code works before that. In Togoder Security an AI model reads the source code of every package version in your lockfile across npm, PyPI, crates.io, Go, RubyGems and Packagist, without running it, and flags behavior such as credential reads, unexpected network calls, obfuscated payloads and code that runs on import. Use the web upload or the API, including from CI (how), from a prepaid balance or with a monthly plan.
It complements cooldowns and pinning, and AI review has false positives and can miss things; the methodology explains verdicts and limits. Identical files get one verdict, which saves work: in our corpus of 1,661 npm and Go package versions hashed on 4 to 6 October 2026, about one in five code files was byte-identical to another file already in the corpus.
Frequently asked questions
What is a software supply chain attack?
It is an attack that compromises something developers already trust, such as an open-source package, a CI action, a build pipeline or a vendor update, so that malicious code reaches every downstream user without attacking them directly.
What is an example of a software supply chain attack?
On 31 March 2026 attackers who had planted a remote access trojan on the axios lead maintainer's PC published axios 1.14.1 and 0.30.4 with a new dependency whose install script dropped a remote access trojan on machines that installed it with scripts enabled. The versions were live for about three hours.
What were the biggest supply chain attacks of 2025 and 2026?
By reach: the Shai-Hulud npm worms (September and November 2025), the chalk and debug compromise (September 2025), the Trivy to LiteLLM cascade and axios (March 2026), TanStack (May 2026) and the ChainDrop worm that hit keyv and 400+ other npm packages (August 2026).
Was there a supply chain attack on axios, and which versions were affected?
Yes. axios 1.14.1 and 0.30.4 were malicious, published on 31 March 2026 from 00:21 UTC and removed at 03:15 UTC. They added the dependency plain-crypto-js@4.2.1, which installed a remote access trojan; 1.14.0 and 0.30.3 are clean.
What is the ChainDrop worm and how is it related to Shai-Hulud?
ChainDrop is an August 2026 npm worm from the same malware family as Shai-Hulud. It started from the keyv maintainer's GitHub account, ran from a preinstall script, stole developer and CI credentials, and republished more than 400 packages with stolen tokens.
How do attackers get malicious code into npm or PyPI packages?
Mostly by hijacking real packages: phishing or infecting a maintainer, stealing a publish token from CI, or abusing a project's release workflow through untrusted pull requests or cache poisoning. Typosquats and hallucinated package names are the long tail.
Does npm audit or Dependabot detect supply chain attacks?
Only after an advisory for the malicious version exists. Hijacked releases are usually live for hours before that, so cooldowns, pinning and reading the code of new versions cover the gap.
Do --ignore-scripts and a lockfile protect me?
Partly. A lockfile stops new malicious versions from entering through version ranges, and disabling install scripts blocks install-time payloads, but payloads that run on import or at Python start, like AsyncAPI and LiteLLM in 2026, still run when your code loads the package.
Do npm provenance and trusted publishing prevent supply chain attacks?
No. They prove which workflow built a package, which helps, but TanStack, AsyncAPI and ChainDrop published malware through the victims' own trusted workflows, so the malicious versions had valid provenance.
How do I prevent supply chain attacks in GitHub Actions?
Pin every action to a full commit SHA, never run pull-request code in pull_request_target or other privileged workflows, keep secrets and caches out of jobs that install dependencies, give the token minimal permissions, and monitor runner network traffic.
What should I do if I installed a compromised package version?
Isolate the machine, remove any persistence such as token monitors before revoking tokens (some 2026 payloads wipe the home directory on revocation), rotate every credential it could read from a clean machine, purge caches and rebuild, then search your GitHub organization for repositories and workflows you did not create.
What is the difference between a supply chain attack and a vulnerability?
A vulnerability is an accidental flaw in legitimate code that you fix by patching. A supply chain attack delivers deliberately malicious code through a trusted channel, so you remove the version, treat affected machines as compromised and rotate their secrets.
Sources
- ENISA Threat Landscape for Supply Chain Attacks, ENISA (Jul 29, 2021)
- Glossary: supply chain attack (CNSSI 4009-2022), NIST CSRC
- #StopRansomware: CL0P Ransomware Gang Exploits CVE-2023-34362 MOVEit Vulnerability (AA23-158A), CISA (Jun 7, 2023)
- Highly evasive attacker leverages SolarWinds supply chain to compromise multiple global victims with SUNBURST backdoor, Mandiant (Google Cloud) (Dec 13, 2020)
- 3CX software supply chain compromise initiated by a prior software supply chain compromise, Mandiant (Google Cloud) (Apr 20, 2023)
- Notepad++ hijacked incident: info update, Notepad++ (Feb 2, 2026)
- 2026 State of the Software Supply Chain: open source malware, Sonatype (Jan 28, 2026)
- Q3 2026 Open Source Malware Index: When Compromise Compounds, Sonatype (Oct 8, 2026)
- ReversingLabs 2026 Software Supply Chain Security Report identifies 73% increase in malicious open-source packages, ReversingLabs (Jan 27, 2026)
- 2026 Data Breach Investigations Report: executive summary, Verizon
- crates.io: malicious crates faster_log and async_println, Rust blog (Sep 24, 2025)
- shopsprint/decimal Go typosquat hides DNS TXT command backdoor, Corgea (May 20, 2026)
- Gartner Identifies Top Security and Risk Management Trends for 2022, Gartner (Mar 7, 2022)
- pnpm v10.16.0 release notes (minimumReleaseAge), pnpm (Sep 12, 2025)
- backdoor in upstream xz/liblzma leading to ssh server compromise, Andres Freund, oss-security (Mar 29, 2024)
- Timeline of the xz open source attack, Russ Cox
- GHSA-jcxm-7wvp-g6p5: @solana/web3.js compromised versions (CVE-2024-54134), GitHub Advisory Database
- Supply-chain attack analysis: Ultralytics, PyPI blog (Dec 11, 2024)
- zizmor would have caught the Ultralytics workflow vulnerability, William Woodruff (Dec 6, 2024)
- GHSA-mrrh-fwg8-r2c3: tj-actions changed-files through 45.0.7 allows remote attackers to discover secrets by reading actions logs (CVE-2025-30066), GitHub Advisory Database
- GitHub Actions Supply Chain Attack: A Targeted Attack on Coinbase Expanded to the Widespread tj-actions/changed-files Incident: Threat Assessment, Palo Alto Networks Unit 42 (Mar 20, 2025)
- GHSA-cxm3-wv7p-598c: malicious versions of Nx and Nx plugins, Nx (GitHub security advisory)
- s1ngularity: supply chain attack leaks secrets on GitHub: everything you need to know, Wiz (Aug 27, 2025)
- npm debug and chalk packages compromised, Aikido Security (Sep 8, 2025)
- Learnings from recent npm supply chain compromises, Datadog Security Labs (Oct 30, 2025)
- Shai-hulud npm attack: What you need to know, ReversingLabs (Oct 10, 2025)
- Our plan for a more secure npm supply chain, GitHub blog (Sep 22, 2025)
- Widespread Supply Chain Compromise Impacting npm Ecosystem, CISA (Sep 23, 2025)
- Shai-Hulud 2.0 Supply Chain Attack: 25K+ Repos Exposing Secrets, Wiz (Nov 24, 2025)
- Second Sha1-Hulud Wave Affects 25,000+ Repositories via npm Preinstall Credential Theft, The Hacker News (Nov 24, 2025)
- Post-mortem: Unauthorized Cline CLI npm publish on February 17, 2026, Cline (Feb 24, 2026)
- GHSA-69fq-xp46-6x23: Trivy ecosystem supply chain temporarily compromised (CVE-2026-33634), Aqua Security (GitHub security advisory) (Mar 21, 2026)
- Security Update: Suspected Supply Chain Incident, LiteLLM (Mar 24, 2026)
- site: site-specific configuration hook (.pth files), Python documentation
- Post Mortem: axios npm supply chain compromise (issue #10636), axios maintainers, GitHub (Apr 2, 2026)
- Postmortem: TanStack npm supply-chain compromise, TanStack (May 11, 2026)
- Mini Shai-Hulud Worm Compromises TanStack, Mistral AI, Guardrails AI & More Packages, The Hacker News (May 12, 2026)
- [SECURITY] Supply chain compromise in mistralai 2.4.6 (issue #523), mistralai/client-python, GitHub
- Postmortem: Nx Console v18.95.0 supply-chain compromise, Nx (May 21, 2026)
- Supply chain compromises impact Nx Console and GitHub repositories, CISA (May 28, 2026)
- Supply Chain Attack Targets Laravel-Lang Packages with Credential Stealer, Aikido Security (May 23, 2026)
- An update on Composer & Packagist supply chain security, Packagist blog (May 27, 2026)
- From package to postinstall payload: Inside the Mastra npm supply chain compromise by Sapphire Sleet, Microsoft Security blog (Jun 17, 2026)
- A forgotten contributor account compromised the entire Mastra npm package scope, Snyk
- Unpacking the AsyncAPI npm supply chain compromise and import-time payload delivery, Microsoft Security blog (Jul 15, 2026)
- 'ChainDrop' worm compromises hundreds of popular npm packages, Datadog Security Labs (Aug 4, 2026)
- Keyv and friends compromised in active Shai-Hulud supply chain attack, Aikido Security (Aug 4, 2026)
- ChainDrop supply chain compromise: anatomy of a self-propagating worm, Microsoft Security blog (Aug 4, 2026)
- Supply chain attack on arrayref, Rust blog (Aug 20, 2026)
- Tensorlake npm Package Compromised: A Worm With a Hostage Token That Wipes Your Machine If You Revoke It, StepSecurity (Oct 8, 2026)
- Contained: Oct 21, 2026 LiteLLM release v1.107.0 Compromised, LiteLLM (Oct 21, 2026)
- The attack chain behind the v1.107.0 LiteLLM release, LiteLLM (Oct 23, 2026)
- Trojanized Claude Code Extension Delivered Persistent SSH Backdoor to Developer Machines, StepSecurity (Oct 30, 2026)
- Malicious Claude Code extension on Cursor planted SSH backdoors, BleepingComputer (Oct 30, 2026)
- Harden-Runner detection: tj-actions/changed-files action is compromised, StepSecurity (Mar 14, 2025)
- Keyv and friends compromised (Aikido update of 4 August 2026, archived 13:57 UTC), Aikido Security via Internet Archive (Aug 4, 2026)
- Shai-Hulud strikes again: CHAINDROP worm hits 400+ npm packages, Elastic Security Labs (Aug 6, 2026)
- Restricting npm bypass-2FA granular access tokens, GitHub changelog (Jul 31, 2026)
- hackerbot-claw: An AI-Powered Bot Actively Exploiting GitHub Actions, StepSecurity (Mar 1, 2026)
- Trivy Compromised: Everything You Need to Know about the Latest Supply Chain Attack, Wiz (Mar 20, 2026)
- TeamPCP deploys CanisterWorm on NPM following Trivy compromise, Aikido Security (Mar 20, 2026)
- Update: Ongoing Checkmarx Supply Chain Security Incident, Checkmarx
- TeamPCP strikes again: Telnyx compromised three days after LiteLLM, Endor Labs (Mar 27, 2026)
- litellm 1.82.8 Supply Chain Attack on PyPI (March 2026), FutureSearch (Mar 24, 2026)
- Supply Chain Attack on Axios Pulls Malicious Dependency from npm, Socket (Mar 31, 2026)
- North Korea-Nexus Threat Actor Compromises Widely Used Axios NPM Package in Supply Chain Attack, Google Threat Intelligence Group (Mar 31, 2026)
- Mitigating the axios npm supply chain compromise, Microsoft Security blog (Apr 1, 2026)
- npm adds preventive account protection for high-impact accounts, GitHub changelog (Jun 25, 2026)
- TanStack npm supply-chain attack: 42 packages compromised with valid SLSA provenance, CybelAngel (May 13, 2026)
- Mini Shai-Hulud strikes again: TanStack and more npm packages compromised, Wiz (May 12, 2026)
- GitHub confirms breach of 3,800 repos via malicious VSCode extension, BleepingComputer (May 20, 2026)
- I don't know what to say. (event-stream issue #116), dominictarr/event-stream, GitHub (Nov 20, 2018)
- Details about the event-stream incident, npm blog (Nov 27, 2018)
- Active exploitation of SolarWinds software, CISA (Dec 13, 2020)
- SolarWinds Corporation Form 8-K, U.S. SEC (Dec 14, 2020)
- Press Briefing by Press Secretary Jen Psaki and Deputy National Security Advisor Anne Neuberger, The White House (Feb 17, 2021)
- Bash Uploader security update, Codecov (Apr 15, 2021)
- Mandiant Security Update: Initial Intrusion Vector, 3CX (Apr 20, 2023)
- We Have a Package for You! A Comprehensive Analysis of Package Hallucinations by Code Generating LLMs, Spracklen et al., USENIX Security 2025
- Investigating incidents in cybersecurity evaluations, Anthropic (Jul 30, 2026)
- Compromised PyTorch-nightly dependency chain between December 25th and December 30th, 2022, PyTorch blog
- Developer sabotages own npm module (node-ipc protestware), The Register (Mar 18, 2022)
- Polyfill supply chain attack hits 100K+ sites, Sansec (Jun 25, 2024)
- npm install-time security and bypass-2FA token deprecation (npm v12), GitHub changelog (Jul 8, 2026)
- npm config: min-release-age, before, allow-git, npm Docs
- npm trusted publishing with OIDC is generally available, GitHub changelog (Jul 31, 2025)
- Staged publishing and new install-time controls for npm, GitHub changelog (May 22, 2026)
- npm publish-time malware scanning and dual-use metadata, GitHub changelog (Jul 28, 2026)
- PyPI now supports digital attestations, PyPI blog (Nov 14, 2024)
- Releases now reject new files after 14 days, PyPI blog (Jul 22, 2026)
- crates.io development update: trusted publishing, Rust blog (Jul 11, 2025)
- crates.io development update: GitLab and Trusted Publishing only mode, Rust blog (Jan 21, 2026)
- Plugins, RubyGems Guides
- SleeperGem: compromised RubyGems drop a persistent backdoor, StepSecurity
- Update on the May spam publishing campaign, RubyGems blog (Sep 11, 2026)
- Trusted Publishing, RubyGems Guides
- Scripts, Composer documentation
- Config: allow-plugins, Composer documentation
- Composer 2.10 release, Packagist blog (May 28, 2026)
- Settings: dependency resolution (minimumReleaseAge, trustPolicy), pnpm docs
- Incident report: phishing attack (pypj.org), PyPI blog (Jul 31, 2025)
- Strengthening npm security: important changes to authentication and token management, GitHub changelog (Sep 29, 2025)
- Mini Shai-Hulud: compromised @antv npm packages enable CI/CD credential theft, Microsoft Security blog (May 20, 2026)
- pnpm v12.0.0 release notes, pnpm (Aug 26, 2026)
- pnpm v11.0.0 release notes, pnpm (Apr 28, 2026)
- Settings: exclude-newer, uv documentation
- pip install: --uploaded-prior-to, pip documentation
- bunfig.toml: install.minimumReleaseAge, Bun Docs
- Settings (.yarnrc.yml): npmMinimalAgeGate, Yarn
- Dependabot options reference: cooldown, GitHub Docs
- Configuration options: minimumReleaseAge, Renovate Docs
- npm-approve-scripts, npm Docs
- Dependabot now detects malware in npm dependencies, GitHub changelog (Mar 17, 2026)
- GitHub Actions policy now supports blocking and SHA pinning actions, GitHub changelog (Aug 15, 2025)
- Actions pull_request_target and environment branch protections changes, GitHub changelog (Nov 7, 2025)
- Safer pull_request_target defaults for GitHub Actions checkout, GitHub changelog (Jun 18, 2026)
- Read-only Actions cache for untrusted triggers, GitHub changelog (Jun 26, 2026)
- A Mini Shai-Hulud has appeared, StepSecurity
- Unvalidated npm trusted publishing configurations now expire, GitHub changelog (Oct 2, 2026)
- Immutable releases are now generally available, GitHub changelog (Oct 28, 2025)