Introduction
On May 11, 2026, an attacker published 84 malicious package versions across 42 @tanstack/* npm packages in under six minutes, distributing credential-stealing malware through one of the JavaScript ecosystem's most widely used library families. The compromise, tracked as CVE-2026-45321 with a CVSS score of 9.6, is notable not for a single novel technique but for the precise chaining of three well-documented vulnerability classes into a single attack that bypassed OIDC-based trusted publishing, SLSA provenance, and npm audit signatures simultaneously.
TanStack provides routing, data fetching, table rendering, and form management libraries primarily 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 supply chain attackers seeking maximum downstream impact.
Technical Information
The attack methodology relied on chaining three distinct vulnerability classes to bypass standard security controls and publish malware under a trusted identity. We will walk through each phase in sequence.
Phase 1: Pwn Request via pull_request_target
The attacker exploited a pull_request_target trigger in the bundle-size.yml workflow within 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 but 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. The fork was renamed 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
During execution of the untrusted code, 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
This key was crafted to match the cache key that the legitimate release workflow would later attempt to restore. The poisoned cache entry was saved at 11:29 UTC, staging the malicious payload for the next phase.
Phase 3: OIDC Token Extraction from Runner Memory
When a legitimate maintainer merged a separate pull request later that day, the release workflow triggered and restored the poisoned cache. The attacker-controlled binaries, now present in the pnpm store, executed during the build process. The payload located the GitHub Actions Runner.Worker process and read its memory via /proc/{pid}/mem to extract the OIDC token.
This token was then used to authenticate direct POST requests to the npm registry, bypassing the workflow's own publish step entirely. Because the token was minted by the legitimate runner in the legitimate repository, the resulting packages carried valid SLSA Build Level 3 provenance attestations. Running npm audit signatures would not flag them.
Payload Architecture
The malware used a dual payload delivery system:
-
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 gathered environment variables to detect CI pipeline execution contexts, 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
This supply chain compromise leaves a rich trail of indicators across package manifests, file systems, network traffic, CI/CD logs, and developer workstations. Detection breaks down into several complementary approaches.
Manifest and Lockfile Inspection
The single most reliable static indicator is an injected optionalDependencies entry in any @tanstack/* package's package.json pointing 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 compromise indicator. 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 only declares ["dist", "src"], so the presence of router_init.js at the package root is itself anomalous:
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 |
A useful heuristic: compromised tarballs are approximately 905 KB compared to ~190 KB for clean versions, a nearly 5x increase. Any SCA tool or registry proxy that tracks tarball sizes over time would flag this 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 the payload executed. Note that the Session Protocol CDN (getsession.org) is a legitimate privacy-focused service being abused, 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 detection signal is the double-tap publishing pattern: each of the 42 compromised packages received exactly two malicious versions, published roughly six minutes apart. Publish-rate alerting (two patch bumps for the same package in under 10 minutes) would catch this.
Persistence Artifacts on Developer Machines
If the malware executed 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 on every VS Code workspace open.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.ymlas 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 "Mini Shai-Hulud" campaign (Dune-themed):
- Git commits authored by
[email protected]with 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, as 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 regex patterns it uses to scan memory and files for credentials. These same patterns can be used defensively:
- 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
A critical detection lesson: 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, not whether the pipeline was behaving as intended. Defenders should not rely solely on SLSA provenance 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 affected package 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 specific version numbers is published in the security advisory GHSA-g7cv-rxg3-hmpx.
Any version of an @tanstack/* package installed or updated after 19:20 UTC on May 11, 2026 and before the affected versions were pulled should be treated 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 running CI/CD pipelines that installed affected packages during the compromise window should treat their environments as breached, as 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



