Yesterday, between 19:20 and 19:26 UTC, six minutes of automated publishing destroyed the trust model of modern JavaScript development.
The way most developers think about software security is already out of date.
The mental model is: write code, review code, ship code. Security lives at the edges - the login form, the API endpoint, the firewall. The code itself, once reviewed and merged, is assumed to be what it says it is.
That assumption died this week.
The attack on TanStack yesterday was not a breach. No password was stolen. No server was compromised. The attacker used the project’s own trusted publishing pipeline, its own cryptographic certificates, its own identity - and produced packages that passed every automated security check currently deployed at scale. The certificate said legitimate. It was not. And the average developer running npm install had no mechanism to know the difference.
This is the supply chain problem in its mature form. The code is not the attack surface anymore. The trust infrastructure around the code is.
In that window, 84 malicious package versions were pushed across 42 packages in the @tanstack namespace. Not by an attacker who stole a password. By TanStack's own legitimate release pipeline, using its own trusted identity, after attacker-controlled code hijacked the CI runner mid-workflow. @tanstack/react-router alone has 12.7 million weekly downloads. Within hours the worm had spread to Mistral AI's official npm SDK, UiPath, Guardrails AI, OpenSearch, and at least 170 packages across both npm and PyPI.
Total cumulative downloads of affected packages: over 518 million.
The repositories the attacker created to receive stolen credentials all contained the same string: “Shai-Hulud: Here We Go Again.”
They named it after the Dune sandworm. The one that lives under the surface on planet Arrakis. And something about a liquid that turns your eyes blue that a stranger gave you at Burning Man while scratching sand out of his crevices from the playa and listening to a banger mashup of Skrillex, David Guetta, dubstep deep techno remix, which seems like a good vibes idea until you have to go to work on Monday and it's not very cool in the office with glowing blue eyes, with all the strange looks and questions it raises, leaving you hanging for a Tuesday meltdown day off.
The attack is Wave 4 of the Mini Shai-Hulud campaign, attributed to a financially motivated threat group called TeamPCP. The earlier waves hit in September and November 2025 and in April 2026. Each iteration builds on the last.
What made Wave 4 different was not the scale. Wave 2 was larger. What made it different was this: for the first time in documented history, a malicious npm package carried valid SLSA Build Level 3 provenance attestation.
SLSA provenance is a cryptographic certificate generated by Sigstore. It is meant to verify that a package was built from a trusted source using a trusted pipeline. It is the current gold standard for supply chain integrity. The certificate said: this package is legitimate. The package was not legitimate.
To understand how that happened, you need to understand the attack chain:
Attack chain: Wave 4, May 11 2026
─────────────────────────────────────────────────────────────────
May 10 Attacker forks TanStack/router as zblgg/configuration
(renamed to avoid fork-list searches)
Malicious commit authored as: claude <[email protected]>
(impersonating the Anthropic Claude GitHub App)
Prefixed [skip ci] to suppress automated CI on push
May 11 PR submitted triggering pull_request_target workflow
Workflow runs attacker's fork code
Malicious pnpm store injected into GitHub Actions cache
Legitimate maintainer PR later merged to main
Release workflow restores the poisoned cache
Attacker code reads OIDC token from runner process memory
(/proc/<pid>/mem - direct memory extraction)
19:20 Attacker uses OIDC token to publish 84 malicious artifacts
19:26 Publishing complete
Valid SLSA Build Level 3 attestation generated automatically
by the legitimate Sigstore stack
19:50 StepSecurity detects and reports to TanStack maintainers
21:30 GitHub security advisory published
Three separate vulnerabilities chained. None sufficient alone. The commit impersonated the Claude GitHub App. The cache poisoning was a known pattern documented in 2024 but not yet patched in this workflow. The OIDC memory extraction is the technical escalation: the attacker never needed npm credentials. They extracted the publishing token directly from the runner’s process memory at runtime.
The worm then did what Shai-Hulud does. It used stolen GitHub tokens to enumerate every package the compromised maintainer controlled and published infected versions of each. Self-propagating. One account becomes dozens. Just like a filthy Harkonnen would do, only with fewer oil bubble baths.
The attack has three distinct phases. Each one creates the condition for the next.
PHASE 1 - PLANT
─────────────────────────────────────────────────────────────────
Attacker forks target repo
└─ Renames fork to look innocuous (zblgg/configuration)
└─ Authors malicious commit as: [email protected]
(impersonates Anthropic's GitHub App - trusted by CI systems)
└─ Prefixes commit [skip ci] so fork's own CI never runs it
Submits PR to legitimate repo
└─ Triggers pull_request_target workflow
└─ This workflow has write permissions - by design, for label bots etc.
└─ Attacker's fork code now runs inside the trusted repo's runner
PHASE 2 - POISON
─────────────────────────────────────────────────────────────────
Malicious code injects a poisoned pnpm store into the
GitHub Actions cache under a cache key the release workflow will use
Attacker waits.
A legitimate maintainer merges a normal PR.
Release workflow runs. Restores cache. Loads poisoned store.
Malicious code now executes inside the release pipeline.
PHASE 3 - HARVEST + PUBLISH
─────────────────────────────────────────────────────────────────
Malicious code reads the OIDC token from runner process memory
(/proc/<pid>/mem - no credentials needed, just process access)
Uses OIDC token to call npm publish
└─ 84 malicious packages published in 6 minutes
└─ Sigstore generates valid SLSA Build Level 3 attestation
automatically - because the legitimate pipeline ran them
Exfiltration via three simultaneous channels:
1. git-tanstack.com (typosquat domain)
2. Session decentralised messenger
3. GitHub API dead drops in commit messages
Dead man's switch activates:
└─ Daemon polls GitHub every 60 seconds
└─ If token revoked → rm -rf ~/ on infected machine
└─ 1-in-6 chance of rm -rf / on systems geolocated to IL or IR
The reason this defeated SLSA provenance is architectural: SLSA verifies that a package was built by a specific pipeline from a specific source. It does not verify that the pipeline itself was uncompromised at build time. Once the attacker's code was inside the runner, the provenance certificate was generated by the legitimate Sigstore stack - correctly - for a malicious build. The certificate was technically accurate. The trust model it was designed to enforce was not.
The payload exfiltrated stolen credentials through three redundant channels simultaneously: a typosquat domain (git-tanstack.com), the Session decentralised messenger network, and GitHub API dead drops embedded in commit messages. The dead man's switch was back: a persistent daemon that polls GitHub every 60 seconds, and runs rm -rf ~/ if the token is revoked. A 1-in-6 chance of running rm -rf / on systems geolocated to Israel or Iran.
The malware checked for Russian-language system configuration and terminated without exfiltrating data if found.
Someone is making geopolitical decisions inside a JavaScript package manager.
Wave 4 is the headline. The context is what matters.
Shai-Hulud campaign timeline
─────────────────────────────────────────────────────────────────
Sep 2025 Wave 1: chalk, debug, 16 packages. 2.6bn weekly downloads.
Attack vector: phishing against maintainer account.
Duration: 2 hours live.
Nov 2025 Wave 2: Shai-Hulud worm v2. Self-propagating.
Dead man's switch introduced.
GitLab, Red Hat issue coordinated advisories.
Apr 2026 Wave 3: SAP packages, Bitwarden CLI, Aqua Security Trivy,
Checkmarx. Security tooling itself compromised.
May 2026 Wave 4: TanStack, Mistral AI, UiPath, Guardrails AI.
First malicious packages with valid SLSA provenance.
170+ packages. 518M+ cumulative downloads.
And behind all of this, the baseline numbers:
npm ecosystem: malicious package growth
─────────────────────────────────────────────────────────────────
2018: 38 malicious packages reported
2024: 2,168 (arXiv, 2025)
2024: 3,000+ (Snyk, 2025)
Q4 2025: 120,612 malware attacks blocked
in a single quarter (Sonatype, 2026)
2025: 454,648 new malicious packages (Sonatype, 2026)
Average transitive dependencies per npm project: 79
Dependencies un-upgraded over a year: 80%
Weekly npm download requests: 9.8 trillion
The average npm project pulls in 79 packages the developer did not explicitly choose. Every one of those is a trust decision made by someone else, at some point, which you are inheriting every time you run npm install. Nobody is auditing 79 packages. The math does not work.
Before Shai-Hulud, before TeamPCP, there was a GitHub account with a sneaky hacker called Jia Tan.
XZ Utils is a compression library. It ships in essentially every Linux distribution. It is the kind of software nobody thinks about, which is precisely why it was chosen.
In October 2021, Jia Tan began contributing to XZ Utils. Small commits. Bug fixes. Nothing suspicious. Patiently over two years, the contributions grew in frequency and quality. The account engaged in mailing list discussions, helped triage issues, and built a consistent record of reliable work. Meanwhile, the project’s sole maintainer, Lasse Collin, was receiving emails from other accounts pressuring him to hand over control. He was unpaid. He was dealing with mental health challenges by his own account. He was one person maintaining critical infrastructure used by millions of machines.
The pressure worked. In 2023, Jia Tan became co-maintainer.
In February 2024, version 5.6.0 shipped with a backdoor embedded not in the source code but in the build system, hidden inside test files. It activated only under specific conditions: Debian or Fedora, systemd linked against the library, x86-64 hardware. It hijacked SSH authentication. CVSS score: 10.0. Maximum possible.
XZ Utils backdoor: CVE-2024-3094
─────────────────────────────────────────────────────────────────
Oct 2021 Jia Tan account created
2021-2023 Legitimate contributions, trust accumulation
2022-2023 Coordinated pressure campaign on Lasse Collin
2023 Jia Tan granted co-maintainer access
Feb 2024 Backdoor shipped in XZ 5.6.0 (CVSS 10.0)
Mar 29 2024 Andres Freund notices SSH authentication is 500ms slow
Investigates. Finds the backdoor.
Debian, Red Hat, Arch roll back immediately.
Half a second. The entire Linux SSH infrastructure nearly compromised by half a second of latency noticed by one efficient engineer, Andres Freund who was annoyed enough to investigate to reduce noise levels in the code he was testing.
The operation spanned two years and three months. State-level patience, state-level resources, a detailed map of the Linux dependency graph. The malicious code was not in the repository. It was in the compiled tarballs. Not the source anyone was reviewing.
Eric Raymond’s thesis in The Cathedral and the Bazaar (1999) is that given enough eyeballs, all bugs are shallow. The XZ attack is a direct falsification of that premise for a specific attack class: supply chain compromise via trusted insider. The eyeballs were on the source code. The malicious code was in the build artifacts.
Here is the question without a comfortable answer.
npm has 2.1 million packages. GitHub has over 420 million repositories. The ecosystem runs on volunteer maintainers, most unpaid, many of them one-person devs. There is no regulatory framework. There is no mandatory quality control. There is no liability structure. The model is: publish what you like, and if someone finds a problem, patch it, it's all good man.
Contrast this with pharmaceuticals. Aviation. Financial systems. Food. These industries have enforced audit requirements, liability frameworks, regulatory bodies with real teeth. A pharmaceutical company that ships a contaminated batch faces legal consequences. An npm maintainer whose account is compromised faces condolences on GitHub.
Bruce Schneier’s Liars and Outliers (2012) frames this precisely: societal trust systems break down when defection becomes individually rational. The open source trust model works when contributing good code is the dominant strategy. Jia Tan demonstrated that defection is possible at the reputation layer, not the code layer. The attack was social before it was technical.
What makes Wave 4 particularly troubling is that TeamPCP defeated the most sophisticated technical countermeasure currently deployed. SLSA provenance was supposed to be the answer to exactly this problem. The certificate said legitimate. The package was not legitimate. The tool designed to restore trust was used to launder it.
Adam Shostack’s Threat Modeling (2014) asks: who is the adversary, what do they want, and what is the weakest point in the chain? The answer in 2026 is: the weakest point is no longer the code. It is the pipeline that builds and signs the code. And now, increasingly, it is the certificate that verifies the pipeline.
Purpose: Practical defender value - what to watch for, where the gaps are
Most incident response playbooks for supply chain attacks focus on detection after installation. Wave 4 changes the timeline: the window between publish and detection was 30 minutes. Most organisations’ scanning runs on a longer cycle than that. By the time your SAST pipeline runs, your developers may already have pulled the infected version.
Registry persistence patterns
Shai-Hulud does not just publish malicious packages - it publishes to every package the compromised maintainer controls. A single account compromise becomes a multi-package event within minutes. In Wave 4, one TanStack maintainer token was used to publish across the entire @tanstack scope. The signal to watch is not individual package anomalies. It is a burst of new versions across a scope or organisation published in a compressed window.
Detection signal: registry burst
─────────────────────────────────────────────────────────────────
Alert on: N versions published from the same publisher account
within X minutes across Y distinct packages
Wave 4 baseline: 84 versions, 42 packages, 6 minutes
Threshold suggestion: >5 packages, <15 minutes → immediate review
Detection gaps in current tooling
Standard dependency scanning (Snyk, Dependabot, npm audit) compares package versions against a known-bad database. That database is populated after a package is flagged. Wave 4’s packages were flagged at 19:50-30 minutes after publish. Any developer who ran npm install in that window got the malicious version before any scanner knew to flag it.
The gap is temporal. Database-driven scanning is retrospective by design. The tooling that caught Wave 4 fastest (StepSecurity) uses behavioural analysis of CI pipelines, not package signature matching. That is the detection model to invest in.
Telemetry to watch
If you are running Node.js services in production, these are the indicators of compromise from Wave 4. None of them require the malicious package to have been identified first:
Runtime IOCs - Wave 4 payload
─────────────────────────────────────────────────────────────────
Network:
DNS lookups to git-tanstack.com or subdomains
Outbound connections to Session network endpoints
Periodic GitHub API calls from a non-CI process (60s interval)
Unexpected POST requests to github.com/repos/*/git/commits
Process:
Node.js process opening /proc/<pid>/mem of another process
npm publish executed from within a CI runner post-build
Child process executing rm -rf with broad path arguments
Filesystem:
New files under ~/.npm or pnpm store with post-install scripts
containing fetch(), exec(), or process.env access
Modification of existing pnpm cache entries
Creation of persistent daemons in startup directories
Environment:
Process reading ACTIONS_ID_TOKEN_REQUEST_URL or
ACTIONS_ID_TOKEN_REQUEST_TOKEN environment variables
outside of an expected GitHub Actions workflow step
The provenance gap
SLSA Level 3 attestation is now a false signal of trust for any package built after a cache-poisoning event. Until GitHub Actions adds runtime integrity verification of the build environment (not just the source), provenance attestation tells you what source code was used, not whether the build environment was clean. Treat provenance as one factor, not a binary safe/unsafe signal.
There is a corner of the developer community that argues all software should be free. Open source, no exceptions. Charging for code is ideologically impure.
The argument is not wrong about principles. Linux is real. The open source track record is real.
But it papers over the economics.
Lasse Collin was maintaining a library present in every Linux distribution, unpaid, alone, while dealing with mental health challenges. That is not a security failure at the code level. It is a predictable outcome of a structural model that places critical infrastructure on individual volunteers with no institutional support. Jia Tan did not exploit bad code. They exploited exhaustion.
The developers building production software in 2026 are paying real money: API costs, server infrastructure, government registrations, legal compliance, documentation, and support. Not everyone has a VC-funded runway. The median indie developer is self-funded, building something they believe in, hoping the revenue arrives before the savings run out.
Peter Steinberger lost money on OpenClaw before pivoting to a commercial model. Most open source project founders know this story from the inside. The peanut gallery on Reddit demanding everything be free has generally not shipped production software at scale, paid for the servers, handled the compliance, or supported the users.
The question is not whether software should be free. The question is who bears the cost of maintaining it safely, and what happens when the answer is nobody in particular. The XZ attack answered that question empirically. The answer is: a state actor with two years of patience and a burned-out maintainer.
I keep VEKTOR Slipstream closed source during active development. I hear about this question regularly, from skepticism to outright disgust.
The practical reason has nothing to do with ideology. It is about sequencing.
Open source at the wrong stage means releasing code before you have found your own bugs. It means community pressure to stabilise public APIs before they are stable. It means anyone who clones the repository at the wrong moment gets the version with the FTS5 mismatch, the opts passthrough that silently drops metadata, the sovereign screener blocking legitimate writes because override is in the RISK_TOKENS list. Not malicious. Just unfinished.
The planned open source path for vex, the memory portability layer, follows the principle: release when the core is stable enough that community contributions help rather than destabilise. The .vmig.jsonl format is already public. The spec is at vektormemory.com. The approach is: earn trust by shipping something that works, then invite scrutiny.
Jia Tan spent two years earning trust through legitimate contributions before exploiting it. The lesson is not that trust is worthless. The lesson is that trust needs a substrate. Working software with a track record. That takes time. Time during which keeping the source closed is a responsible choice, not a political one.
The npm ecosystem is not ungovernable. It is ungoverned. Those are different problems.
Wave 4 broke SLSA provenance attestation as a trust anchor. That is a significant escalation. The response needs to match the escalation.
Hardening the pipeline, not just the code. The TanStack attack exploited pull_request_target, a known vulnerable pattern. GitHub published the attack pattern in 2024. TanStack was still running it in 2026. Security advisories that do not produce workflow changes are not security advisories. They are documentation of future incidents.
Funded maintainership for load-bearing packages. The OpenSSF has mechanisms. The Linux Foundation has mechanisms. The gap is that nobody has built a reliable dependency graph showing which packages are structurally critical to the global software supply chain. Sonatype’s data gets closest. This is a solvable problem that has not been solved because no institution with money has decided it is their problem yet.
Regulatory frameworks. The EU Cyber Resilience Act (2024) begins to establish liability for software products. It does not yet cover open source maintainers in any useful way. The US has nothing equivalent. Software supply chain regulation is where food safety regulation was in the early twentieth century: the consequences are visible, the framework does not exist, and someone will eventually decide the cost of inaction is higher than the cost of governance.
Isolation as default. The Register’s coverage of the Wave 4 attack ended with a line worth repeating: “running everyday commands like npm install is unsafe, and software development is now best done in isolated, ephemeral environments." That is not a fringe security opinion anymore. That is the current practical baseline.
The instinct after an attack like this is to look for a tool that would have stopped it. There is no single tool. Wave 4 defeated the most sophisticated technical control currently deployed at scale. That is the point.
What works is layered defence with honest expectations about each layer’s limits:
Slow down the install loop. Most teams have npm install running on every CI trigger, every local dev environment spin-up, every Docker build. Each of those is an implicit trust decision. Add a lockfile review step before installs in production pipelines. Not automated scanning - human eyes on what changed and why.
Shrink the blast radius. The dead man’s switch only works if the compromised process has broad filesystem access. Running Node.js build pipelines in ephemeral, isolated environments with minimal permissions turns rm -rf ~/ from a catastrophe into a nuisance. The Register’s assessment after Wave 4 is worth repeating: everyday development is best done in isolated, ephemeral environments. That is not a fringe opinion anymore.
Watch your own publishers. If your organisation publishes to npm, audit who has publish access and from which machines. Wave 4 used legitimate maintainer tokens. Your tokens are the attack surface. Rotate them. Use granular scopes. Enable npm’s token audit log and alert on unexpected publish events.
Fund the infrastructure you depend on. Lasse Collin was maintaining XZ Utils alone. The TanStack maintainer whose token was compromised is not a corporation. The open source supply chain runs on individuals. The OpenSSF Alpha-Omega project and the Sovereign Tech Fund both fund critical maintainers directly. If your business depends on open source packages - and it does - this is not charity. It is security spend.
The worm is in the registry. It will be back. The question is not whether you can stop the next wave entirely. The question is whether you have enough layers that when one fails, the others hold.
As of this writing, StepSecurity has confirmed propagation continues: Intercom’s official Node.js SDK was compromised at 14:41 UTC today, 36 hours after the TanStack attack, via a hijacked OIDC publishing pipeline from yesterday’s victims.
One account. Then dozens. Then hundreds.
The ground keeps moving, get the thumper out Duncan.
Remember the tooth… Atreides
Incident reports (Wave 4, May 2026)
XZ Utils
Reports and data
Books
Regulation
Published by Vektor Memory. The .vmig.jsonl memory portability spec: vektormemory.com/spec. VEKTOR Slipstream SDK: vektormemory.com/downloads