Back
cybersecurity

A Self-Replicating Worm Just Poisoned 170 npm and PyPI Packages

On May 11, 2026, a worm called Mini Shai-Hulud compromised 84 malicious package versions across the TanStack ecosystem in six minutes — without stealing a single credential. Here's what happened, how it spread, and what to do if your environment was affected.

On May 11, 2026, between 19:20 and 19:26 UTC, 84 malicious package versions were published across 42 packages in the @tanstack namespace. The publish took six minutes. By the time an external security researcher filed a bug report, roughly 20 minutes after the first malicious version appeared, the worm had already begun spreading to packages maintained by other developers.

This is the fifth wave of a supply chain campaign called Shai-Hulud.


What happened

The immediate victim was TanStack, a collection of JavaScript utilities used across the React ecosystem. Its router package alone receives over 12.7 million weekly downloads. The attack did not start there — it started with a GitHub account named voicproducoes, created on May 10, which submitted a pull request to the TanStack/router repository.

That pull request was the foothold. The attacker used it to poison a shared GitHub Actions cache and push an orphaned commit — a commit with no parent history, disconnected from the repository's main branch — to a fork. Because of how GitHub stores git objects across a repository and its forks in a shared graph, that commit remained reachable from the parent repository's CI environment.

When a legitimate maintainer later pushed to the main branch, the release workflow ran. It restored the poisoned cache, fetched the attacker's orphaned commit as an optional dependency, and executed a 2.3 MB obfuscated file called router_init.js via the prepare lifecycle hook of the orphaned @tanstack/setup optional dependency. The workflow then used TanStack's own trusted publishing identity to push 84 malicious package versions to the npm registry — each carrying a cryptographically valid provenance certificate.

No npm token was stolen. No maintainer account was compromised. The packages were published by TanStack's legitimate release pipeline, using its own identity, after the attacker hijacked the runner mid-workflow.

The worm then drove its own expansion. It swept the infected environment for credentials — npm tokens, GitHub personal access tokens, AWS keys, Kubernetes service account tokens, Vault secrets — and used any npm tokens it found to enumerate packages maintained by the same developer. For each one, it injected the malicious payload and published a new compromised version. Stolen credentials were committed to public GitHub repositories bearing the description "Shai-Hulud: Here We Go Again," and exfiltrated through a second channel: the Session messenger peer-to-peer network, where traffic is indistinguishable from encrypted messaging.

By end of day on May 12, the campaign had touched over 170 packages across both npm and PyPI, including libraries from Mistral AI, UiPath, OpenSearch, and Guardrails AI. Cumulative downloads across affected packages exceeded 518 million.

The incident has been assigned CVE-2026-45321 with a CVSS score of 9.6.


Who is behind it

The campaign is attributed to a threat group called TeamPCP, a cloud-focused actor that security researchers at StepSecurity, Endor Labs, and Snyk link to at least four prior supply chain operations in 2026 alone: the Aqua Security Trivy compromise in March, the Bitwarden CLI package in April, an SAP npm package campaign in late April, and now this.

The earlier Shai-Hulud waves — the original in September 2025 and the broader Shai-Hulud 2.0 campaign in November 2025 — used the same worm toolchain. Attribution of those earlier waves to TeamPCP specifically has not been confirmed by researchers. The connection between the 2025 and 2026 activity is assessed with moderate confidence based on shared infrastructure patterns; definitive attribution for the 2025 waves remains open.

On May 12, malware research collective vx-underground reported that the worm's source code had been published to GitHub under the message "A Gift From TeamPCP." The code includes deployment instructions, a full credential collection suite, and — according to OX Security — hooks targeting Claude Code configuration files. At least one external contributor had already submitted a pull request adding FreeBSD support within hours of the release. The toolchain is no longer exclusively TeamPCP's to deploy.


What the worm targets

The payload is designed for developer and CI/CD environments. Once router_init.js executes, it deploys a weaponized copy of TruffleHog bundled inside router_init.js to scan the host for credentials. Targets include:

  • npm and GitHub tokens reachable from the install environment
  • AWS, GCP, Azure, and Kubernetes credentials
  • HashiCorp Vault and Azure Key Vault tokens
  • SSH keys and Postman API collections
  • Browser-stored credentials and session cookies
  • .claude/settings.json and VS Code task configuration files

Credentials are staged in a cloud.json artifact, committed to a public GitHub repository created under the victim's account, and simultaneously exfiltrated through the Session network. The public repository serves as both an exfiltration mechanism and an indicator of compromise: any unexpected repository named "Shai-Hulud" in a developer's GitHub account is a confirmed signal of infection.


What made this wave different

Every prior supply chain attack in this family relied at some point on stealing a long-lived credential — an npm token sitting on a developer's machine, a GitHub personal access token stored in a CI environment variable. The defense against that class of attack is to remove long-lived tokens entirely, replacing them with short-lived credentials minted at publish time using OIDC trusted publishing.

TanStack had done exactly that. There was no npm publish token to steal. Two-factor authentication was enabled on every maintainer account. Every release carried a SLSA Build Level 3 provenance attestation, the highest standard currently defined for verifiable build integrity.

None of it stopped the attack. The worm did not steal a credential. It triggered a workflow run against a commit it controlled, waited for that workflow to mint a fresh OIDC publish token during the build, and extracted that token from the runner process's memory via /proc/*/mem before the token expired.

The workflow was real, the signing was real, and the packages were compromised. Nothing in the chain of verification was false — it just wasn't checking for this.


What to do if your environment is affected

If any @tanstack/* package was installed on May 11, 2026, treat the install host as compromised.

Rotate credentials in this order: npm tokens first, then GitHub personal access tokens, then cloud provider keys (AWS, GCP, Azure), then Kubernetes service accounts and Vault tokens. Do not revoke tokens before isolating and imaging the affected machine — the worm includes a destructive mechanism that triggers on token revocation.

Check your GitHub account for any repository named "Shai-Hulud" or bearing the description string "Here We Go Again." Its presence is a confirmed indicator. Also check CI/CD environments for unexpected router_init.js files larger than 1 MB, unexpected entries in package lockfiles referencing the commit hash 79ac49eedf774dd4b0cfa308722bc463cfe5885c, and persistence artifacts in .claude/ and .vscode/tasks.json.

Confirmed-clean TanStack families include @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, @tanstack/store, and @tanstack/start (the meta-package only, not @tanstack/start-*). The full list of compromised versions is documented in GHSA-g7cv-rxg3-hmpx.

For PyPI exposure: mistralai==2.4.6 is confirmed malicious. Mistral AI states that no version 2.4.6 was ever legitimately released — no commits landed on May 11 and no tag exists for that version.


The broader picture

This wave did not represent a failure of negligence. TanStack's security posture was, by current industry standards, exemplary. The attack worked because it operated in the spaces between controls that each assumed the others held firm.

The pull_request_target workflow pattern has been documented as dangerous since at least 2021 and was the basis of the tj-actions/changed-files compromise in March 2025. It remains in widespread use across major open-source projects. GitHub Actions cache scope crosses the fork-to-base trust boundary by design. OIDC token lifetimes are short enough to prevent reuse but long enough for a workflow to extract them from process memory and act on them.

None of these is a zero-day. They are configuration patterns that, individually, appear reasonable and are widely recommended. Combined, they form an attack surface that bypasses the specific authentication controls that were in play.

The TanStack postmortem frames it plainly: the chain only works because each vulnerability bridges the trust boundary the others assumed. That framing is worth sitting with. Identifying which individual control to fix is less useful than asking what assumptions each control was making about the others — and whether those assumptions were ever made explicit.