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.
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.