Introduction
On May 11, 2026, an attacker published 84 malicious package versions across 42 @tanstack/* npm packages in under six minutes. The versions carried credential-stealing malware, and they belong to one of the most widely used library families in the JavaScript ecosystem. The compromise is tracked as CVE-2026-45321, with a CVSS score of 9.6. None of its techniques is new. It chains three well-documented vulnerability classes into a single attack, precisely enough to bypass OIDC-based trusted publishing, SLSA provenance, and npm audit signatures at the same time.
TanStack provides routing, data fetching, table rendering, and form management libraries, mainly for React and Solid applications. The project's packages receive millions of weekly downloads, and its router library is a foundational dependency in a large number of production web applications. That reach made TanStack an attractive target for a supply chain attacker who wanted the widest possible downstream impact.
Technical Information
The attack chained three distinct vulnerability classes to get past standard security controls and publish malware under a trusted identity.
Phase 1: Pwn Request via pull_request_target
The attacker exploited a pull_request_target trigger in the bundle-size.yml workflow in the TanStack/router repository. The pull_request_target event is a well-documented footgun in GitHub Actions. It runs the workflow definition from the base branch, in the context of the base repository, with access to repository secrets and write permissions. If the workflow checks out and executes code from the pull request head, untrusted code runs with elevated privileges.
The attacker created a fork of TanStack/router on May 10, 2026 at 17:16 UTC and renamed it to zblgg/configuration to evade fork-list searches. On May 11 at 11:11 UTC, the attacker force-pushed a malicious commit onto the pull request head. When the bundle-size.yml workflow triggered, it checked out and ran the attacker's code in the privileged base repository context.
Phase 2: Cache Poisoning Across the Fork/Base Trust Boundary
While the untrusted code ran, the malware wrote a 1.1 GB payload into the pnpm store directory. The GitHub Actions cache mechanism then saved this poisoned store under a deterministic key:
Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11
The key was crafted to match the cache key that the legitimate release workflow would later try to restore. The poisoned cache entry was saved at 11:29 UTC, and the payload sat there waiting for the next phase.
Phase 3: OIDC Token Extraction from Runner Memory
Later that day, a legitimate maintainer merged a separate pull request. The release workflow triggered and restored the poisoned cache, and the attacker-controlled binaries in the pnpm store executed during the build. The payload located the GitHub Actions Runner.Worker process and read its memory via /proc/{pid}/mem to extract the OIDC token.
The token was then used to authenticate direct POST requests to the npm registry, which bypassed the workflow's own publish step entirely. Because the legitimate runner in the legitimate repository had minted the token, the resulting packages carried valid SLSA Build Level 3 provenance attestations. Running npm audit signatures would not flag them.
Payload Architecture
The malware delivered its payload in two ways:
-
An
optionalDependenciesentry was injected into each compromised package'spackage.json, pointing to a fictitious@tanstack/setuppackage hosted via a GitHub commit reference. This package'spreparescript silently downloaded a Bun binary. -
A 2.3 MB obfuscated file called
router_init.jswas planted at the root of every compromised tarball. This primary payload read environment variables to detect whether it was running in a CI pipeline, then exfiltrated secrets to attacker-controlled infrastructure atfilev2.getsession.organdseed1.getsession.org.
Attack Timeline
| Time (UTC) | Event |
|---|---|
| 2026-05-10 17:16 | Attacker creates a fork of TanStack/router |
| 2026-05-11 11:11 | Force push lands malicious commit on the pull request head |
| 2026-05-11 11:29 | Poisoned cache entry saved to GitHub Actions |
| 2026-05-11 19:20 | First wave of malicious packages published to npm |
| 2026-05-11 19:26 | Second wave of malicious packages published to npm |
| 2026-05-11 19:50 | External researcher reports the compromise |
Each of the 42 compromised packages received exactly two malicious versions, published roughly six minutes apart.
Detection Methods
The compromise leaves a wide trail of indicators in package manifests, file systems, network traffic, CI/CD logs, and developer workstations. Each needs its own check.
Manifest and Lockfile Inspection
The single most reliable static indicator is an injected optionalDependencies entry in any @tanstack/* package's package.json that points to a fork commit:
"optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" }
This entry should never appear in legitimate TanStack packages. Any @tanstack/* dependency that references a github: URL with a pinned commit hash is a strong sign of compromise. To check your own projects:
# npm grep "@tanstack/" package-lock.json | grep -v node_modules # pnpm grep "@tanstack/" pnpm-lock.yaml # yarn grep "@tanstack/" yarn.lock # Also check for non-TanStack propagation targets grep -E "(draftlab|draftauth|taskflow-corp|tolka)" package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null
Cross-reference any resolved versions against the affected version lists published in GHSA-g7cv-rxg3-hmpx.
File System Artifacts
A ~2.3 MB file called router_init.js was planted at the root of every compromised tarball. The package's files field declares only ["dist", "src"], so a router_init.js at the package root is anomalous on its own:
find node_modules -name "router_init.js" -type f 2>/dev/null grep -r "@tanstack/setup" node_modules/*/package.json 2>/dev/null
The payload hashes (SHA-256) are confirmed across all compromised packages:
| File | SHA-256 |
|---|---|
router_init.js | ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c |
tanstack_runner.js | 2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96 |
@tanstack/setup package.json | 7c12d8614c624c70d6dd6fc2ee289332474abaa38f70ebe2cdef064923ca3a9b |
Tarball size is another useful heuristic. Compromised tarballs are about 905 KB, against ~190 KB for clean versions, an increase of nearly 5x. Any SCA tool or registry proxy that tracks tarball sizes over time would flag the jump.
Network Indicators of Compromise
The malware uses two exfiltration channels, plus cloud metadata and second-stage download endpoints. Monitor or block the following:
C2 and exfiltration domains: filev2.getsession.org, seed1.getsession.org, seed2.getsession.org, seed3.getsession.org, api.masscan.cloud, git-tanstack.com
Second-stage payload URLs: https://litter.catbox.moe/h8nc9u.js, https://litter.catbox.moe/7rrc6l.mjs
Cloud metadata targets: 169.254.169.254 (AWS EC2 IMDS), 169.254.170.2 (ECS/Fargate task metadata), 127.0.0.1:8200 (local HashiCorp Vault)
Outbound HTTPS connections to filev2.getsession.org or api.masscan.cloud during npm install or build steps are a definitive sign that the payload executed. The Session Protocol CDN (getsession.org) is a legitimate privacy-focused service being abused here, so its domains may not appear on standard threat intelligence blocklists. Defenders should add them explicitly for this campaign.
The malware also pins against a specific TLS certificate: CN=seed1.getsession.org, O=Oxen Privacy Tech Foundation (Melbourne, AU, valid until 2033). TLS inspection tools can use this certificate fingerprint as an additional detection signal.
CI/CD Runtime Detection
In GitHub Actions environments, the payload runs a Python script that scrapes the Runner.Worker process memory via /proc/{pid}/mem to extract every secret, including masked ones. Detection signals include:
- Unexpected
python3processes reading/proc/*/memduring CI runs - Process command lines showing
Runner.Workerbeing targeted by another process - Execution of
bun run tanstack_runner.jsin CI process logs (the payload requires the Bun runtime, which is not standard in most CI environments)
The attacker's workflow runs that performed the malicious publishes are:
https://github.com/TanStack/router/actions/runs/25613093674(attempt 4)https://github.com/TanStack/router/actions/runs/25691781302
A secondary signal is the double-tap publishing pattern, in which each of the 42 compromised packages received exactly two malicious versions, published roughly six minutes apart. Publish-rate alerting that fires on two patch bumps for the same package in under 10 minutes would catch it.
Persistence Artifacts on Developer Machines
If the malware runs on a developer machine (via npm install), it drops several persistence mechanisms:
.claude/settings.jsonwith aSessionStarthook that re-executes the malware on every Claude Code session.vscode/tasks.jsonwith afolderOpentask that re-executes the malware every time a VS Code workspace opens.claude/router_runtime.js,.claude/setup.mjs,.vscode/setup.mjsas dropped payload and setup scripts- macOS:
~/Library/LaunchAgents/com.user.gh-token-monitor.plistfor persistent GitHub token monitoring - Linux:
~/.config/systemd/user/gh-token-monitor.serviceperforming the same function .github/workflows/codeql_analysis.yml, an injected workflow disguised as CodeQL that exfiltrates all repository secrets on push or deployment events using${{ toJSON(secrets) }}
Campaign-Specific Markers
The attacker leaves distinctive fingerprints attributed to the Dune-themed "Mini Shai-Hulud" campaign:
- Git commits authored by
[email protected]with the messagechore: update dependencies(dead-drop commits for encrypted exfiltration data) - Branch names matching
dependabot/github_actions/format/{dune-word}(e.g., atreides, fremen, sandworm, harkonnen, melange), designed to mimic legitimate Dependabot branches - npm tokens with the description
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner. Important: do not revoke such tokens before forensically imaging the affected machine. The payload reportedly includes a destructive wipe routine triggered by revocation. - Worm marker repositories:
siridar-ghola-567,tleilaxu-ornithopter-43, described as "A Mini Shai-Hulud has Appeared"
Token Pattern Matching
The deobfuscated payload contains the regex patterns it uses to scan memory and files for credentials. Defenders can use the same patterns:
- npm tokens:
npm_[A-Za-z0-9]{36,} - GitHub tokens:
gh[op]_[A-Za-z0-9]{36} - Vault tokens:
hvs\.[A-Za-z0-9_-]{24,} - Kubernetes tokens:
eyJhbGciOiJSUzI1NiIsImtpZCI6[\w\-.]+ - AWS access keys:
AKIA[0-9A-Z]{16}
SLSA Provenance Caveat
The compromised packages carry valid SLSA Build Level 3 provenance attestations. Running npm audit signatures will not flag them, because the attacker minted a legitimate OIDC token from the runner's own memory and used it with the Sigstore stack (Fulcio and Rekor) to generate valid attestations. Provenance confirms which pipeline produced the artifact. It does not confirm that the pipeline behaved as intended, so defenders should not treat SLSA provenance alone as a safety guarantee.
Attacker Infrastructure
| Indicator Type | Value |
|---|---|
| GitHub accounts | zblgg (ID: 127806521), voicproducoes (ID: 269549300) |
| Attacker fork | zblgg/configuration (renamed fork of TanStack/router) |
| Secondary fork | voicproducoes/router |
| Orphan payload commit | 79ac49eedf774dd4b0cfa308722bc463cfe5885c |
| Malicious commit in fork | 65bf499d16a5e8d25ba95d69ec9790a6dd4a1f14 |
| Forged commit identity | claude <[email protected]> |
| Fictitious package | @tanstack/setup |
| Payload filenames | router_init.js, tanstack_runner.js |
| Exfiltration domains | filev2.getsession.org, seed1.getsession.org |
| Poisoned cache key | Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 |
Affected Systems and Versions
The compromise affected 42 distinct @tanstack/* packages on the npm registry. Each received exactly two malicious versions, for a total of 84 malicious versions published between 19:20 and 19:26 UTC on May 11, 2026. The complete list of affected packages and version numbers is in the security advisory GHSA-g7cv-rxg3-hmpx.
Treat any version of an @tanstack/* package installed or updated after 19:20 UTC on May 11, 2026, and before the affected versions were pulled, as potentially compromised. Versions published before 19:00 UTC on May 11, 2026 are confirmed clean.
The following package families have been confirmed clean and were not affected: @tanstack/query, @tanstack/table, @tanstack/form, @tanstack/virtual, @tanstack/store, and the @tanstack/start meta package.
Organizations whose CI/CD pipelines installed affected packages during the compromise window should treat those environments as breached. The malware was specifically designed to exfiltrate CI/CD secrets, cloud provider credentials, GitHub tokens, and SSH keys.
References
- NVD: CVE-2026-45321
- CVE Record: CVE-2026-45321
- GitHub Security Advisory: GHSA-g7cv-rxg3-hmpx
- TanStack/router Issue #7383
- TanStack Postmortem: npm Supply Chain Compromise
- StepSecurity: TeamPCP's Mini Shai-Hulud Is Back
- GitHub Security Lab: Keeping your GitHub Actions and workflows secure Part 1
- CodeQL: Cache Poisoning via caching of untrusted files
- OWASP: GitHub Actions Security Cheat Sheet



