This is why i removed nodejs from my userspace completely (and cargo, and uv) and just work in containers now. For review, I no longer diff npm packages on git sources but on npm tarballs and after i have it do postinstall, also in docker, with diffoci.
Could maybe do something with less friction by templating bwrap, but for now, the container overhead does help with retention too (docker save has been promoted to be among my fav tools now)
Socket's alert caught the packages right after publish: the injected drainers were in @tanstack/query, table, router, etc., via a maintainer machine compromise. Run npm ls @tanstack/* now and cross-check the tarball timestamps against the clean commits. Optimism's container + diffoci workflow catches post-install changes, but the real gap is still the unauthenticated publish path—until npm enforces provenance or reproducible builds by default, this stays a recurring tax on every JS dep tree.
Heads up for anyone still thinking this was a stolen npm token or a hacked maintainer laptop, neither happened. No long-lived publish creds got pulled off any human's machine. The chain was different and worse.
The crack was a bundle-size.yml workflow on pull_request_target. That trigger runs fork code with base-repo permissions and shares the cache scope between PR runs and main. Attacker opened a PR, poisoned the pnpm store cache entry, and waited. The real release workflow on main came along, restored the poisoned cache, executed an attacker binary during the build, and from there it read /proc/<pid>/mem on the Runner.Worker process to scrape the OIDC token that GitHub mints fresh every run when id-token: write is set.
With that OIDC token they hit npm's OIDC exchange and minted per-package publish tokens on the fly. So provenance attestation is "valid" because the legit pipeline really did sign the artifact, it just signed the wrong one. SLSA Build L3 does not catch this. It proves which pipeline ran, not whether the pipeline ran clean.
Self-spread by hitting registry.npmjs.org/-/v1/search?text=maintainer:<user> and republishing every package the victim maintains. 42 packages, 84 versions, six minute window. UiPath, Mistral, Draftauth, others cascaded from there.
The dead man switch is the part folks aren't talking enough about: token description literally reads IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner. Revoke and the runtime nukes home. That is why some maintainers are slow to rotate.
Audit your repos for pull_request_target plus any cache action. That is the pattern. pull_request is fine, pull_request_target with cache is a footgun.
Wow
This is why i removed nodejs from my userspace completely (and
cargo, anduv) and just work in containers now. For review, I no longer diff npm packages on git sources but on npm tarballs and after i have it do postinstall, also in docker, withdiffoci.Could maybe do something with less friction by templating
bwrap, but for now, the container overhead does help with retention too (docker savehas been promoted to be among my fav tools now)https://twiiit.com/SocketSecurity/status/2053950165665386546
Socket's alert caught the packages right after publish: the injected drainers were in @tanstack/query, table, router, etc., via a maintainer machine compromise. Run
npm ls @tanstack/*now and cross-check the tarball timestamps against the clean commits. Optimism's container + diffoci workflow catches post-install changes, but the real gap is still the unauthenticated publish path—until npm enforces provenance or reproducible builds by default, this stays a recurring tax on every JS dep tree.All these supply chain attacks lately with npm, making me paranoid about using the ecosystem.
Modern software supply chains are honestly terrifying. One compromised package and half the internet starts sweating.
Heads up for anyone still thinking this was a stolen npm token or a hacked maintainer laptop, neither happened. No long-lived publish creds got pulled off any human's machine. The chain was different and worse.
The crack was a
bundle-size.ymlworkflow onpull_request_target. That trigger runs fork code with base-repo permissions and shares the cache scope between PR runs and main. Attacker opened a PR, poisoned the pnpm store cache entry, and waited. The real release workflow on main came along, restored the poisoned cache, executed an attacker binary during the build, and from there it read/proc/<pid>/memon theRunner.Workerprocess to scrape the OIDC token that GitHub mints fresh every run whenid-token: writeis set.With that OIDC token they hit npm's OIDC exchange and minted per-package publish tokens on the fly. So provenance attestation is "valid" because the legit pipeline really did sign the artifact, it just signed the wrong one. SLSA Build L3 does not catch this. It proves which pipeline ran, not whether the pipeline ran clean.
Self-spread by hitting
registry.npmjs.org/-/v1/search?text=maintainer:<user>and republishing every package the victim maintains. 42 packages, 84 versions, six minute window. UiPath, Mistral, Draftauth, others cascaded from there.The dead man switch is the part folks aren't talking enough about: token description literally reads
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner. Revoke and the runtime nukes home. That is why some maintainers are slow to rotate.Audit your repos for
pull_request_targetplus any cache action. That is the pattern.pull_requestis fine,pull_request_targetwith cache is a footgun.