On May 11, 2026, an attacker published 84 malicious package versions across 42 @tanstack/* npm packages in under six minutes. The attack combined three chained vulnerabilities in GitHub Actions to mint legitimate OIDC tokens and publish credential-stealing malware through TanStack's own release pipeline. No npm tokens were stolen. The pipeline itself became the weapon.
This was not an isolated incident. It was part of the broader Mini Shai-Hulud campaign by threat actor TeamPCP, which simultaneously hit @mistralai/mistralai, @uipath/*, @opensearch-project/opensearch, and the Python guardrails-ai package - over 170 packages across npm and PyPI in a single coordinated wave. The TanStack compromise alone affected packages with approximately 12 million weekly downloads.
This article breaks down exactly how the attack worked, what the malware does, how to check if you're affected, and the concrete steps every engineering team should take to harden their CI/CD pipelines and dependency management against this class of supply chain attack.
Table of Contents
- Attack Timeline: 6 Minutes That Shook npm
- The Three-Vulnerability Chain
- What the Malware Does
- Affected Packages and How to Check
- The Broader Mini Shai-Hulud Campaign
- Incident Response Playbook
- Hardening GitHub Actions Against Cache Poisoning
- Dependency Defense: Lockfiles, Provenance, and Script Policies
- Lessons for the JavaScript Ecosystem
- Secure Your CI/CD Pipeline with Lushbinary
1Attack Timeline: 6 Minutes That Shook npm
The attack unfolded in two distinct phases: a silent cache-poisoning phase on May 10-11, followed by a rapid detonation phase that published malware through TanStack's legitimate release workflow.
Phase 1: Cache Poisoning (May 10-11)
| Time (UTC) | Event |
|---|---|
| May 10, 17:16 | Attacker creates fork zblgg/configuration (renamed to evade fork-list searches) |
| May 10, 23:29 | Malicious commit authored under fabricated identity claude <claude@users.noreply.github.com>. Adds a ~30,000-line bundled JS payload |
| May 11, 10:49 | PR #7378 opened against TanStack/router#main. Triggers pull_request_target workflows automatically |
| May 11, 11:11 | Force-push lands the malicious commit. Workflow executes attacker code, poisoning the pnpm store cache |
| May 11, 11:29 | Poisoned 1.1 GB cache entry saved to GitHub Actions cache under a key matching the release workflow |
| May 11, 11:31 | PR force-pushed back to a 0-file no-op, closed, and branch deleted. Cache poison persists invisibly |
Phase 2: Detonation (May 11, 19:15-19:26 UTC)
When a legitimate maintainer merged a PR to main, the release workflow restored the poisoned cache. Attacker binaries extracted OIDC tokens from the runner's process memory and published 84 malicious versions directly to the npm registry - all within 6 minutes.
Key Insight
The attacker chose a payload that broke tests, causing the workflow's normal publish step to skip. The malware published packages from within the test/cleanup phase instead. A more careful attacker who didn't break tests could have remained undetected for hours longer.
2The Three-Vulnerability Chain
This attack required chaining three separate vulnerabilities. No single flaw alone was sufficient - each bridged a trust boundary the others assumed was intact.
Vulnerability 1: pull_request_target "Pwn Request"
TanStack's bundle-size.yml workflow used the pull_request_target trigger, which runs in the context of the base repository (with access to secrets and write permissions) even when triggered by a fork PR. The workflow checked out the fork's code and executed it - giving the attacker code execution in the base repo's CI environment.
# The dangerous pattern (simplified)
on:
pull_request_target:
paths: ['packages/**']
jobs:
benchmark-pr:
steps:
- uses: actions/checkout@v6.0.2
with:
ref: refs/pull/${{ github.event.pull_request.number }}/merge
# ^ This checks out FORK code in the BASE repo context
- run: pnpm nx run @benchmarks/bundle-size:build
# ^ Executes attacker-controlled code with base repo privilegesVulnerability 2: GitHub Actions Cache Poisoning
The actions/cache@v5 post-job save uses a runner-internal token, not the workflow GITHUB_TOKEN. Setting permissions: contents: read does not block cache writes. Cache scope is shared between pull_request_target runs (which use the base repo's scope) and pushes to main. The attacker wrote a poisoned pnpm store under the exact cache key the release workflow would later restore.
Vulnerability 3: OIDC Token Extraction from Runner Memory
The release workflow declared id-token: write for npm OIDC trusted publishing. When the poisoned cache was restored, attacker binaries read /proc/<pid>/mem of the GitHub Actions Runner.Worker process to extract the OIDC token from memory. This token authenticated directly to registry.npmjs.org - bypassing the workflow's defined publish step entirely.
3What the Malware Does
The payload is delivered through two vectors: an optionalDependencies entry pointing to an orphan commit that executes a prepare script, and an embedded ~2.3 MB obfuscated file router_init.js placed directly in the package tarball. When npm install, pnpm install, or yarn install runs against an affected version, the malware executes automatically via lifecycle scripts.
Credential Harvesting
The malware targets a comprehensive list of credential sources:
- Cloud credentials: AWS IMDSv2 / Secrets Manager, GCP metadata service, Azure managed identity
- CI/CD tokens: GitHub Actions OIDC, GitLab CI, CircleCI environment variables
- Container orchestration: Kubernetes service-account tokens, HashiCorp Vault tokens
- Package registry:
~/.npmrctokens, npm publish tokens - Source control: GitHub tokens (env vars, gh CLI,
.git-credentials), SSH private keys - Password vaults (Python variant): 1Password, Bitwarden local stores
Triple Exfiltration Channels
Stolen credentials are exfiltrated via three redundant channels, making takedown significantly harder:
Typosquat Domain
git-tanstack[.]com - attacker-controlled C2
Session Network
Decentralized, encrypted, takedown-resistant messenger
GitHub Dead Drops
Creates repos using stolen tokens with Dune-themed descriptions
Self-Propagation (Worm Behavior)
The malware enumerates other packages the victim maintains via the npm registry API and republishes them with the same injection. This is what makes it a worm - it spreads through the ecosystem without human intervention, using each compromised developer as a stepping stone to more packages.
Destructive Wiper Daemon
Critical: Remove Daemon Before Revoking Tokens
If the malware finds valid GitHub tokens, it installs a persistent gh-token-monitor daemon (macOS LaunchAgent or Linux systemd) that polls GitHub every 60 seconds. If the token is revoked, the daemon executes rm -rf ~/, wiping the home directory. The daemon auto-exits after 24 hours. Always remove the daemon first, then revoke tokens.
Persistence locations to check:
- macOS:
~/Library/LaunchAgents/com.user.gh-token-monitor.plist - Linux:
~/.config/systemd/user/gh-token-monitor.service
4Affected Packages and How to Check
42 @tanstack/* packages were affected, with 84 malicious versions total (two per package). The TanStack team confirmed these package families were not compromised:
@tanstack/query*@tanstack/table*@tanstack/form*@tanstack/virtual*@tanstack/store@tanstack/start (meta-package only)Key affected packages include @tanstack/react-router (versions 1.169.5 and 1.169.8), @tanstack/router-core, @tanstack/router-cli, @tanstack/react-start, and all @tanstack/solid-* and @tanstack/vue-* router packages. The full list is available in GitHub Security Advisory GHSA-g7cv-rxg3-hmpx.
How to Check Your Project
Search your lockfile for the IOC fingerprint - any affected package will contain this in its manifest:
# Check for the malicious optionalDependencies entry grep -r "79ac49eedf774dd4b0cfa308722bc463cfe5885c" node_modules/ # Check for the malicious payload file find node_modules/@tanstack -name "router_init.js" -size +2M # Check for persistence artifacts ls ~/Library/LaunchAgents/com.user.gh-token-monitor.plist 2>/dev/null ls ~/.config/systemd/user/gh-token-monitor.service 2>/dev/null # Check IDE directories for leftover artifacts find . -path "*/.claude/router_runtime.js" -o -path "*/.vscode/setup.mjs"
5The Broader Mini Shai-Hulud Campaign
The TanStack compromise was one node in a coordinated attack wave. TeamPCP, the threat actor behind the campaign, simultaneously targeted multiple high-value namespaces across npm and PyPI. The Mini Shai-Hulud campaign is a continuation of attacks that first targeted SAP CAP packages in late April 2026, then hit over 500 packages including CrowdStrike-maintained libraries in September 2025.
| Target | Ecosystem | Impact |
|---|---|---|
@tanstack/* | npm | 84 versions, 42 packages, ~12M weekly downloads |
@mistralai/mistralai | npm + PyPI | Official Mistral AI TypeScript and Python clients |
@uipath/* | npm | 60+ enterprise automation packages |
@opensearch-project/opensearch | npm | Official OpenSearch client |
guardrails-ai | PyPI | LLM guardrails framework |
The Python variant operates differently - it downloads an unobfuscated modular credential stealer from the C2 domain. It only executes on Linux machines and exits if the system uses Russian language settings or has fewer than four CPUs. Notably, if the system locale is set to Israel or Iran, it invokes a random number generator and, on a 1-in-6 chance, plays an audio file at full volume and attempts to wipe the filesystem.
Attribution
Wiz assesses with high confidence that this is the work of TeamPCP, the same operators behind the SAP, Checkmarx, Bitwarden, Lightning, Intercom, and Trivy compromises. The attack reuses public tradecraft including the verbatim memory-dump script from the tj-actions compromise of March 2025.
6Incident Response Playbook
If you suspect exposure, follow this sequence carefully. The order matters - removing the persistence daemon before revoking tokens prevents the wiper from triggering.
Check for persistence daemon
Search for gh-token-monitor on all developer machines and CI runners. Remove it immediately if found.
launchctl list | grep gh-token-monitor systemctl --user status gh-token-monitor
Remove the daemon
Unload and delete the persistence mechanism before touching any tokens.
# macOS launchctl unload ~/Library/LaunchAgents/com.user.gh-token-monitor.plist rm ~/Library/LaunchAgents/com.user.gh-token-monitor.plist # Linux systemctl --user stop gh-token-monitor systemctl --user disable gh-token-monitor rm ~/.config/systemd/user/gh-token-monitor.service
Rotate all credentials
Rotate GitHub tokens, npm tokens, AWS credentials, Vault tokens, Kubernetes service accounts, and SSH keys reachable from the install host.
# Revoke GitHub PATs gh auth logout # Rotate npm tokens npm token revoke <token-id> # AWS credential rotation aws iam create-access-key --user-name <user> aws iam delete-access-key --access-key-id <old-key>
Audit IDE and project directories
Check .claude/ and .vscode/ directories for router_runtime.js or setup.mjs artifacts that persist after npm uninstall.
find ~/Projects -name 'router_runtime.js' -o -name 'tanstack_runner.js' find ~/Projects -path '*/.claude/setup.mjs'
Block C2 infrastructure
Block the attacker's command-and-control domains at DNS or proxy level.
# Add to DNS blocklist or /etc/hosts # git-tanstack.com # *.getsession.org (if not using Session messenger)
Update affected packages
Update to the latest clean versions. All malicious versions have been deprecated.
# Remove node_modules and lockfile, reinstall rm -rf node_modules package-lock.json npm install # Verify no affected versions remain npm ls @tanstack/react-router
7Hardening GitHub Actions Against Cache Poisoning
The TanStack attack exploited a well-documented class of GitHub Actions vulnerability. Here are the concrete mitigations every team maintaining open-source packages should implement:
1. Never Execute Fork Code in pull_request_target
The pull_request_target event runs with base repository privileges. If you must use it (for labeling, commenting), never check out or execute the PR's code. Use pull_request instead for any workflow that builds or tests fork contributions.
# SAFE: pull_request_target for metadata-only operations
on:
pull_request_target:
types: [opened, synchronize]
jobs:
label:
runs-on: ubuntu-latest
permissions:
pull-requests: write
steps:
- uses: actions/labeler@v5 # Never checks out fork code
# SAFE: pull_request for build/test (runs in fork context)
on:
pull_request:
paths: ['packages/**']
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4 # Checks out fork code safely2. Pin All Third-Party Actions to Full SHA Hashes
Floating refs like @v6.0.2 or @main can be retargeted. Pin to the full commit SHA to prevent supply chain attacks on the actions themselves.
# Bad: floating tag - uses: actions/checkout@v4 # Good: pinned to full SHA - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
3. Separate Build and Publish Workflows
Never grant id-token: write to a workflow that also runs tests or builds. Separate the publish step into its own workflow with minimal permissions, triggered only after tests pass in a separate job.
4. Disable Caching in Sensitive Workflows
If your release workflow uses OIDC publishing, consider disabling cache restoration entirely. The performance cost of a fresh install is far less than the risk of cache poisoning.
5. Add Repository Owner Guards
Add an if condition to ensure workflows only run when triggered from the expected repository, not from forks:
jobs:
publish:
if: github.repository == 'YourOrg/your-repo'
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read8Dependency Defense: Lockfiles, Provenance, and Script Policies
Even if your CI/CD pipeline is hardened, your project can still be affected as a downstream consumer. Here are the layers of defense for dependency management:
Lockfile Discipline
- Always commit your lockfile (
package-lock.json,pnpm-lock.yaml,yarn.lock) - Use
npm ci(notnpm install) in CI to ensure exact lockfile versions are installed - Review lockfile diffs in PRs - unexpected version bumps or new dependencies are red flags
- Pin exact versions in
package.jsonfor critical dependencies (no^or~ranges)
Disable Lifecycle Scripts
The Shai-Hulud worm relies on npm lifecycle hooks (preinstall, prepare) to execute. Disabling these eliminates the primary infection vector:
# .npmrc - disable scripts globally ignore-scripts=true # Or selectively allow scripts for trusted packages # pnpm: use .pnpmfile.cjs to control which packages can run scripts # npm: use --ignore-scripts flag in CI
npm Provenance Verification
npm provenance attestation links a published package to its source commit and build workflow. While it wouldn't have prevented the TanStack attack (the malware published through the legitimate workflow), it provides an audit trail and enables detection of publishes from unexpected workflow steps.
# Verify provenance of installed packages npm audit signatures # Publish with provenance (for package maintainers) npm publish --provenance
Supply Chain Security Tools
Layer automated scanning into your workflow:
- Socket.dev: Detects suspicious package behavior (network calls, filesystem access, obfuscated code) before installation
- StepSecurity Harden-Runner: Monitors GitHub Actions runner network and file activity in real-time - this is what detected the TanStack attack within 20 minutes
- npm audit: Checks for known vulnerabilities in your dependency tree
- Snyk / Mend: Continuous monitoring with automated PR fixes for vulnerable dependencies
- GitHub Dependabot: Automated security updates with lockfile-aware PRs
9Lessons for the JavaScript Ecosystem
The TanStack compromise exposes systemic issues in how the JavaScript ecosystem handles trust, publishing, and CI/CD security. These are not TanStack-specific problems - they affect every open-source project using GitHub Actions and npm.
What Went Right
- External researchers at StepSecurity detected and reported the compromise within 20 minutes of the first malicious publish
- The TanStack team coordinated immediately across timezones and began deprecating packages within an hour
- The detection community had clear IOC patterns published within hours
- npm security engaged quickly to pull tarballs from the registry
What Needs to Change
- OIDC trusted publishing needs per-publish review. Once configured, any code path in the workflow can mint a publish-capable token. There is no mechanism to restrict which workflow step can publish.
- npm's unpublish policy creates dangerous delays. The "no unpublish if dependents exist" policy means malicious tarballs remain installable for hours while waiting for npm security to pull them server-side.
- GitHub Actions cache scope crosses trust boundaries. Cache entries from
pull_request_targetruns share scope withpushworkflows on the same branch. This is a known design issue that requires conscious mitigation. - No internal publish monitoring. TanStack learned about the compromise from a third party. Projects need automated alerting on unexpected publishes.
- Maintainer count equals blast radius. Seven maintainers on the npm scope means seven credential-theft targets for the same blast radius.
The Bigger Picture
In 2025, Sonatype identified over 454,600 new malicious open-source packages, with over 99% on npm. The Shai-Hulud campaign represents a new class of attack: self-propagating worms that spread through legitimate CI/CD pipelines without stealing credentials directly. The attack surface is not just your code - it's your entire build infrastructure. Every repository with a workflow file is a potential target.
10Secure Your CI/CD Pipeline with Lushbinary
Supply chain attacks are not going away - they are accelerating. Lushbinary helps engineering teams audit and harden their CI/CD pipelines, implement defense-in-depth dependency management, and build incident response playbooks before the next compromise hits.
Our security engineering services include:
- GitHub Actions workflow audit and hardening (pull_request_target elimination, SHA pinning, permission minimization)
- npm/PyPI supply chain security implementation (lockfile policies, script blocking, provenance verification)
- CI/CD pipeline architecture with least-privilege publishing and separated build/release workflows
- Automated dependency scanning integration (Socket.dev, StepSecurity, Snyk)
- Incident response planning and credential rotation automation
Free Security Assessment
Worried your GitHub Actions workflows are vulnerable to cache poisoning? Lushbinary offers a free CI/CD security assessment. We'll audit your workflows, identify pull_request_target risks, and deliver a hardening roadmap - no obligation.
Frequently Asked Questions
Was my project affected by the TanStack npm supply chain attack?
If you installed any @tanstack/* package between 19:20 and 21:00 UTC on May 11, 2026, check your lockfile for the 84 affected versions across 42 packages. Confirmed-safe families include @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, and @tanstack/store.
What credentials should I rotate after the TanStack compromise?
Rotate all AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials reachable from any host that installed an affected version. Check for the gh-token-monitor persistence daemon before revoking GitHub tokens to avoid triggering the wiper payload.
How did the attacker publish packages without stealing npm tokens?
The attacker poisoned the GitHub Actions cache via a pull_request_target workflow, then extracted OIDC tokens from the runner process memory when the release workflow ran on main. These OIDC tokens authenticated directly to the npm registry without needing stored npm credentials.
What is the Mini Shai-Hulud worm and how does it spread?
Mini Shai-Hulud is a self-propagating npm worm by threat actor TeamPCP. It harvests credentials from CI/CD environments, uses stolen npm tokens and GitHub OIDC tokens to republish poisoned versions of other packages the victim maintains, spreading through the ecosystem without human intervention.
How can I protect my GitHub Actions workflows from cache poisoning attacks?
Never run pull_request_target workflows that execute fork code. Pin all third-party actions to full SHA hashes. Separate build and publish into distinct workflows with minimal permissions. Use npm provenance attestation and consider disabling lifecycle scripts with --ignore-scripts.
Sources
- TanStack Official Postmortem (May 11, 2026)
- Wiz Threat Intelligence: Mini Shai-Hulud Strikes Again (May 12, 2026)
- GitHub Security Advisory GHSA-g7cv-rxg3-hmpx
- The Register: Cache-poisoning caper turns TanStack npm packages toxic (May 12, 2026)
- The Hacker News: Mini Shai-Hulud Worm Compromises TanStack, Mistral AI & More (May 12, 2026)
- GitHub Security Lab: Keeping your GitHub Actions and workflows secure
Content was rephrased for compliance with licensing restrictions. Technical details sourced from official postmortems and security advisories as of May 13, 2026. Attack details and IOCs may be updated as the investigation continues - always verify on the original sources.
Harden Your CI/CD Before the Next Attack
Get a free security assessment of your GitHub Actions workflows and dependency management. We'll identify cache poisoning risks, pull_request_target vulnerabilities, and deliver a hardening roadmap.
Ready to Build Something Great?
Get a free 30-minute strategy call. We'll map out your project, timeline, and tech stack - no strings attached.
Prefer email? Reach us directly:

