TL;DR
On May 11, 2026, attackers published 84 poisoned versions of TanStack npm packages in a six-minute window. They never stole a token. They hijacked the maintainer's own CI pipeline and let it sign the malware for them. The packages had valid Sigstore provenance. The ecosystem has a new problem.
The Numbers
If you installed a TanStack package (Query, Router, Table, Form), there is a small chance you ran malware on May 11, 2026. The window was short and the damage was contained, but the technique behind it warrants close attention.
Within 48 hours the campaign expanded to 172 packages and 403 malicious versions across npm and PyPI, including releases from Mistral AI, UiPath, OpenSearch, and Guardrails AI.[1] The cumulative download count of affected packages: 518 million.[4]
The campaign is called Mini Shai-Hulud. Attribution points to a group tracked as TeamPCP, also known as DeadCatx3, PCPcat, ShellForce, and CipherForce. These are the same operators behind the March 2026 Aqua Trivy compromise and the April 2026 Bitwarden CLI npm compromise.[5]
What Actually Happened, in Plain Terms
TanStack had implemented most of the controls the security community recommends:
- Two-factor authentication on every maintainer account
- OIDC trusted publishing instead of long-lived npm tokens
- Signed Sigstore provenance on every release
None of it mattered. The attackers never tried to steal a credential. They went one layer deeper and compromised the build pipeline itself. When the legitimate pipeline ran, it published the attacker's code under TanStack's identity, signed it, attested it, and tagged it as authentic. From npm's point of view, every malicious release was a real release.
TanStack had locked the front door, the back door, and the windows. The attackers slipped a forged blueprint into the locksmith's toolbox.
The Attack Chain, Step by Step
The attackers chained three known vulnerability classes in GitHub Actions. None of them are new individually; together, they are devastating.
Fork & Rename
TanStack/router is forked and renamed to zblgg/configuration to evade discovery.
Evades fork-list discovery
Open PR → triggers pull_request_target
The PR triggers a workflow that runs fork code with the base repo's secrets and permissions.
Classic "Pwn Request" misconfiguration
Poison Actions cache
Instead of publishing directly, a malicious pnpm store is written to the shared GitHub Actions cache.
Cache is shared across branches
Maintainer merges a PR
The release workflow restores the poisoned cache. Attacker code now runs with publish privileges.
OIDC token extracted from /proc/pid/mem
The poisoned code extracts the OIDC token from the runner's process memory and publishes 84 packages to npm, as if it were TanStack itself.
Valid Sigstore provenance on every version
Steps 1 and 2: The Pwn Request
The attacker forked the TanStack/router repository on GitHub, renamed the fork to zblgg/configuration to reduce searchability, then opened a pull request back to TanStack. That PR triggered a workflow using GitHub Actions's pull_request_target event.
Step 3: Cache Poisoning
The forked PR did not try to publish anything directly. Instead, it wrote a malicious pnpm store to the GitHub Actions cache. GitHub's Actions cache is shared across branches of the same repository, including caches written from fork pull requests. That shared scope is the trust boundary that should not exist but does.
Step 4: The Waiting Game
The attackers then waited. Days later, a TanStack maintainer merged a completely unrelated pull request to main. The release workflow ran, restored the poisoned cache, and the attacker's code began executing inside TanStack's trusted CI runner.
The Payload: OIDC Token Extraction
The poisoned install script read GitHub's OIDC token directly from the runner's process memory (/proc/<pid>/mem). With that token, the attacker authenticated to npm as the legitimate TanStack/router repository via npm's trusted-publisher binding and published 84 malicious versions across 42 packages in six minutes.
What the Malware Does to You
Every poisoned package contained a new file: router_init.js, a 2.3 MB obfuscated JavaScript bundle that runs at install time.[4] It behaves differently depending on where it runs.
On CI Runners and Developer Machines
- Steals GitHub environment variables, npm tokens, SSH private keys, AWS / GCP / Azure credentials, and Kubernetes service-account tokens
- Exfiltrates over three redundant channels: the typosquat domain
git-tanstack[.]com, the Session messenger network, and GitHub API "dead drops"[3] - Worm propagation: uses any stolen npm publish tokens to publish poisoned versions of additional packages the victim has write access to
On Developer Workstations
The malware installs a persistent daemon called gh-token-monitor via a macOS LaunchAgent or a Linux systemd unit. The daemon polls GitHub every 60 seconds. When it receives a 40X response (indicating the stolen token has been revoked), it attempts to execute rm -rf ~/.[3]
Deep dive: the Russian language check and PyPI variant
The malware checks the system locale and terminates without exfiltrating data if it detects Russian. This is a known signature of certain Eastern European threat actors and the simplest way the security community infers rough geographic origin.
A separate variant targeted PyPI: mistralai@2.4.6 and guardrails-ai@0.10.1 were both published with a 13-line trojan that downloads and executes git-tanstack[.]com/tmp/transformers.pyz. The Python variant is Linux-only, has the same Russian-locale check, and exits if fewer than four CPUs are present, serving as a basic VM/sandbox evasion check.[3]
Why This Attack Matters Beyond TanStack
According to Snyk's analysis, this is the first npm supply chain attack to publish packages that are cryptographically indistinguishable from legitimate ones by provenance attestation.[5] Wiz and VentureBeat reach the same conclusion in their technical breakdowns.[3][9]
That threshold makes this campaign architecturally significant. It reveals several facts about the ecosystem:
- Sigstore badges and SLSA provenance are necessary but insufficient trust signals on their own
- OIDC trusted publishing protects against credential theft but does not protect against a compromised CI runner
- 2FA and the absence of long-lived tokens do not constitute a complete defense
Supply chain attacks have moved one layer down the stack, from compromising package publishers to compromising the build infrastructure of package publishers. Defenders need behavioral visibility during installs and builds, beyond attestation checks at the registry.
What You Should Actually Do
Most effective defenses are configuration changes, requiring no new tools or vendors. Stacked together, they would have blocked every wave of this campaign.
1. Enable Release Cooldowns
Malicious npm packages are typically detected and removed within hours. The TanStack versions were detected publicly within 20 minutes.[1] A time-based delay before installing newly published versions eliminates the exposure window almost entirely.
# npm 11.10+; npm uses DAYS
min-release-age=7# pnpm 10.16+; pnpm uses MINUTES (10080 = 7 days)
minimumReleaseAge: 10080
minimumReleaseAgeExclude:
- "@your-org/*"# Yarn 4.10+; Yarn uses MINUTES
npmMinimalAgeGate: 10080
npmPreapprovedPackages:
- "@your-org/*"# Bun 1.3+; Bun uses SECONDS (604800 = 7 days)
[install]
minimumReleaseAge = 604800
minimumReleaseAgeExcludes = ["@your-org/*"]version: 2
updates:
- package-ecosystem: npm
directory: "/"
schedule: { interval: daily }
cooldown:
default-days: 72. Disable Lifecycle Scripts in Dependencies
The TanStack payload also abused a prepare lifecycle hook on an optionalDependency (the orphan-committed @tanstack/setup package). pnpm v10+ disables postinstall scripts by default and requires explicit allow-listing via onlyBuiltDependencies in package.json. For npm and Yarn, use --ignore-scripts and selectively enable only trusted packages.
3. Harden Your Own CI/CD Pipelines
This is where the actual attack occurred, and where most teams have the weakest controls.
- Never check out PR code inside a
pull_request_targetworkflow. Use the plainpull_requestevent for anything that touches untrusted contributions. - Set
permissions: id-token: noneat the workflow level. Only grantid-token: writeto the specific job that publishes. - Pin third-party GitHub Actions by commit SHA, never by tag.
- Add repository-owner guards to workflows that run with elevated permissions.
- Treat the GitHub Actions cache as untrusted across the fork-base boundary.
4. Reduce Uncached, Uncontrolled Installs
npx, pnpm dlx, and bunx perform live, uncached resolutions without cooldown enforcement on every invocation. Each run is an opportunity to fetch a compromised version. Pin frequently used tools as devDependencies instead.
5. Block Known Indicators at the Network Edge
At DNS or your egress proxy, block:
git-tanstack[.]com*.getsession.org83.142.209[.]194
6. Lockfile Discipline
Commit package-lock.json or pnpm-lock.yaml. In CI, always use npm ci or pnpm install --frozen-lockfile. This eliminates surprise resolution between local development and production.
If You Installed an Affected Version
Any developer or CI environment that ran npm, pnpm, or yarn install against an affected @tanstack/* version on May 11, 2026 should be considered compromised. The response order matters.
- Isolate the host or runner first; do not revoke tokens yet
- Image the system for forensics
- Check
.claude/and.vscode/directories for persisted payload files such asrouter_runtime.jsorsetup.mjs, which survivenpm uninstall - Check for
gh-token-monitorpersistence in~/Library/LaunchAgents/(macOS) or~/.config/systemd/user/(Linux) - Rotate, in order: npm tokens, GitHub PATs and OIDC federations, cloud credentials (AWS / GCP / Azure), SSH keys, CI/CD secrets, Kubernetes service-account tokens
- Pin all
@tanstack/*dependencies to versions published before 2026-05-11 19:00 UTC or to the patched releases listed in GitHub Security Advisory GHSA-g7cv-rxg3-hmpx[2] - Reinstall from a clean lockfile
The Bigger Picture
Mini Shai-Hulud is the most architecturally significant npm supply chain attack of 2026. It demonstrates that an attacker who can execute code inside a target's GitHub Actions runner can produce malicious packages that are cryptographically indistinguishable from legitimate ones.
The highest-leverage defenses already exist in every major package manager: release cooldowns, lifecycle-script restrictions, locked-down CI workflows, disciplined lockfile use. Most teams have not turned them on.
Turn them on this week.
Sources
- [1]TanStack postmortem — tanstack.com
- [2]GitHub Security Advisory GHSA-g7cv-rxg3-hmpx — github.com
- [3]Wiz Research, "Mini Shai-Hulud Strikes Again" — www.wiz.io
- [4]Socket, "TanStack npm Packages Compromised" — socket.dev
- [5]Snyk, "TanStack npm Packages Hit by Mini Shai-Hulud" — snyk.io
- [6]Endor Labs, "Shai-Hulud Compromises the @tanstack Ecosystem" — www.endorlabs.com
- [7]The Hacker News, "Mini Shai-Hulud Worm Compromises TanStack" — thehackernews.com
- [8]Orca Security, "Supply Chain Worm Attack Analysis" — orca.security
- [9]VentureBeat, "Shai-Hulud Worm 172 npm/PyPI Packages" — venturebeat.com
- [10]Phoenix Security, "TeamPCP Campaign Analysis" — phoenix.security
- [11]Aikido, "Mini Shai-Hulud is Back" — www.aikido.dev
- [12]Infosecurity Magazine, initial disclosure — www.infosecurity-magazine.com
- [13]NHS England Digital Cyber Alert CC-4781 — digital.nhs.uk
- [14]pnpm Supply Chain Security Documentation — pnpm.io
- [15]Socket, "npm Introduces minimumReleaseAge" — socket.dev
