The Attack in a Nutshell
On May 11, 2026, between 19:20 and 19:26 UTC, an attacker published 84 malicious versions across 42 @tanstack/* npm packages. The attack combined three known GitHub Actions vulnerabilities: the pull_request_target "Pwn Request" pattern, cache poisoning across the fork↔base trust boundary, and runtime memory extraction of an OIDC token from the GitHub Actions runner process. No npm tokens were stolen, and the npm publish workflow itself was not compromised.
Timeline of Events
Pre-attack: Cache Poisoning Phase (May 10–11)
- Fork creation: Attacker creates fork
github.com/zblgg/configuration(a fork of TanStack/router, renamed to evade fork-list searches). - Malicious commit: A commit with message
[skip ci]addspackages/history/vite_setup.mjs(~30,000 lines of bundled JS) under a fabricated identityclaude. - PR opened: PR #7378 titled "WIP: simplify history build" triggers
pull_request_targetworkflows (bundle-size.ymlandlabeler.yml). Thepr.ymlworkflow (which usespull_request) does not run, blocked pending approval. - Cache poison: The
bundle-size.ymlworkflow checks out the PR merge ref, runspnpm installandpnpm nx run @benchmarks/bundle-size:build, executingvite_setup.mjs. This script writes malicious data into the pnpm store directory. Theactions/cache@v5post-step saves the poisoned cache under keyLinux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11— a key that the legitimaterelease.ymlworkflow would compute on the next push tomain. - Cleanup: Attacker force-pushes the PR back to current main HEAD, making it a 0-file no-op, then closes PR and deletes branch. Cache poison persists.
Detonation: Publish Phase (May 11, 19:15–19:26)
- Trigger: Manuel merges two PRs (#7369 and #7382) to
main, each triggeringrelease.yml. - Cache restore: Both workflow runs restore the poisoned cache. The malicious binaries are now on disk.
- OIDC token theft: The malware locates the GitHub Actions Runner.Worker process via
/proc/*/cmdline, reads/proc//mapsand/proc//memto dump memory, and extracts the OIDC token (which the runner mints lazily whenid-token: writeis set). This technique was previously used in the tj-actions/changed-files compromise of March 2025. - Malicious publish: The malware uses the stolen OIDC token to POST directly to
registry.npmjs.org, publishing two malicious versions per package (e.g.,@tanstack/history@1.161.9and@tanstack/history@1.161.12). The legitimate "Publish Packages" step is skipped because tests fail.
What the Malware Does
When a developer runs npm install, pnpm install, or yarn install against an affected version, npm resolves the malicious optionalDependencies entry:
"optionalDependencies": {
"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
This fetches an orphan payload commit from the fork network and runs its prepare lifecycle script, which executes a ~2.3 MB obfuscated router_init.js. The script:
- Harvests credentials from AWS IMDS / Secrets Manager, GCP metadata, Kubernetes service-account tokens, Vault tokens,
~/.npmrc, GitHub tokens (env, gh CLI,.git-credentials), SSH private keys - Exfiltrates over the Session/Oxen messenger file-upload network (
filev2.getsession.org,seed{1,2,3}.getsession.org) — end-to-end encrypted with no attacker-controlled C2 - Self-propagates: enumerates other packages the victim maintains via
registry.npmjs.org/-/v1/search?text=maintainer:and republishes them with the same injection
Detection and Response
- 20 minutes: External researcher
carliniopens issue #7383 with full analysis. npm security is notified. - ~20:00 UTC: Manuel acknowledges the incident, removes all other team push permissions on GitHub.
- ~21:00 UTC: Comprehensive scan confirms 42 packages, 84 versions affected. Tanner begins npm deprecation. Public disclosure on Twitter/X/LinkedIn/Bluesky.
- 21:30 UTC: Cache entries purged. Hardening PR merged:
bundle-size.ymlrestructured,repository_ownerguards added, third-party action refs pinned to SHAs. Official GitHub Security Advisory published.
Three Vulnerabilities Chained
- pull_request_target "Pwn Request" in
bundle-size.yml: The workflow checked out the fork's PR-merge ref and ran a build, giving fork code access to the base repo's cache scope.actions/cache@v5's post-job save is not gated bypermissions: contents: read. - Cache poisoning across trust boundaries: The poisoned cache was keyed to match what
release.ymlwould look up on push tomain. This is a known GitHub Actions design issue (Adnan Khan, 2024). - OIDC token memory extraction: The workflow had
id-token: write(needed for npm OIDC trusted publishing). Malicious binaries on disk could dump the runner's memory and extract the token, then use it to publish to npm directly.
IOC Fingerprints
- Manifest:
"optionalDependencies": { "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c" } - File:
router_init.js(~2.3 MB, package root, not in"files") - Cache key:
Linux-pnpm-store-6f9233a50def742c09fde54f56553d6b449a535adf87d4083690539f49ae4da11 - 2nd-stage payload URLs:
https://litter.catbox.moe/h8nc9u.js,https://litter.catbox.moe/7rrc6l.mjs - Exfiltration network:
filev2.getsession.org,seed{1,2,3}.getsession.org - Forged commit identity:
claude - Attacker accounts:
zblgg(id 127806521),voicproducoes(id 269549300) - Attacker fork:
github.com/zblgg/configuration(fork of TanStack/router renamed) - Orphan payload commit:
79ac49eedf774dd4b0cfa308722bc463cfe5885c
What You Should Do Now
If you installed any affected version on 2026-05-11, rotate AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH credentials reachable from the install host. Treat the host as potentially compromised. All affected versions have been deprecated; npm security has been engaged to pull tarballs from the registry.
Mitigations for Maintainers
- Avoid
pull_request_targetwith checkout of fork code. If you must use it, add explicitrepository_ownerchecks and pin action refs to SHAs. - Use separate cache scopes for PR vs. push workflows, or disable cache for
pull_request_targetworkflows. - For OIDC publishing, consider using a separate workflow with minimal permissions and no cache restore from PR-scoped caches.
- Monitor for unexpected cache key collisions and consider cache prefixing by branch.





