All writing

The Day SLSA Provenance Lied

A 6-minute window on May 11 broke a signal the supply-chain world had treated as clean: valid SLSA attestation on 84 malicious npm packages.

We were reading Hacker News at some point after dinner on May 11 when the thread appeared. The title said something about TanStack. We almost scrolled past it. We use @tanstack/react-router. Most people building anything serious in React do. We clicked because the word "compromise" was in the first comment, not the headline.

By the time we finished the thread we had opened three terminal windows. One running npm ls --depth=0 | grep tanstack. One with the GitHub advisory GHSA-g7cv-rxg3-hmpx pulled up. One where we were trying to remember whether our CI had run that afternoon.

The packages turned out fine for us. Our last install had been the day before. But sitting there in those 20 minutes of uncertainty, we kept coming back to one sentence buried about halfway down the postmortem TanStack published the next day. The packages the attacker shipped carried valid SLSA provenance attestations.

We read it twice. Then we read the section that followed it, which explained that Sigstore had verified the build correctly, that the provenance was legitimate, and that the code running through the pipeline had simply not been safe.

That is the thing worth sitting with.

The six-minute window

Between 19:20 and 19:26 UTC on May 11, an attacker published 84 malicious versions across 42 packages in the @tanstack scope. The full batch landed in six minutes. An external researcher named ashishkurmi from StepSecurity noticed an unusual optionalDependencies entry pointing to a GitHub fork and flagged it. No internal alert triggered on TanStack's side.

TanStack deprecated the malicious versions 1 hour 43 minutes after the first publish. npm finished pulling tarballs nearly four hours after that. The packages targeted the Router/Start monorepo specifically. Query, Table, Form, Virtual, Store, and AI were not affected.

@tanstack/react-router pulls 12.7 million downloads a week.

Six minutes to publish. Four-plus hours to contain. That gap is the real number to hold in your head, not because TanStack responded slowly (they did not), but because it describes how fast malicious artifacts can move through a dependency graph before anyone can act.

What the attestation said

SLSA provenance is one of the strongest signals the npm ecosystem has built toward trusted package distribution. The premise is simple: a signed attestation that records the specific source commit, the specific workflow, and the specific repository where the build ran. You can verify it cryptographically. The package either came from where the maintainer says it came from, or it did not.

The TanStack packages said: they came from the right repository and the right workflow. Sigstore agreed. Every cryptographic check passed.

A signed receipt for a poisoned box is still a poisoned box.

The attacker did not forge the attestation or break the signing keys. They got inside the pipeline before the signing happened. The build infrastructure generated legitimate credentials mid-run, and the attacker used those credentials. From Sigstore's perspective, the attestation told the truth about the provenance of the build. The problem: contamination arrived before the build started.

Three primitives that chain

The attack is worth understanding in sequence because each step exploits a different assumption.

The first step was a Pwn Request, which is a specific GitHub Actions pattern. When a workflow uses pull_request_target as its trigger, it runs with the permissions of the base repository rather than the fork. The intent is to let maintainers post comments or run analysis on fork PRs without giving fork code access to secrets. TanStack's bundle-size.yml workflow had this trigger and also checked out the pull request's code and ran it. Fork code. Base repository privileges.

The attacker opened a PR. The workflow ran the fork's code with full base context.

The second step was cache poisoning across trust boundaries. The malicious fork code wrote a 1.1 GB cache entry under the exact key that the legitimate release workflow would later restore. This is the boundary crossing: the bundle-size workflow (low trust, triggered by PRs) and the release workflow (high trust, triggered by maintainer merges) shared a cache key namespace. The attacker wrote from the low-trust context. The high-trust context read without re-validating. The poisoned cache sat there for eight hours before the release workflow pulled it.

The third step was OIDC token extraction. GitHub Actions supports OIDC-based publishing, which replaces long-lived npm credentials with short-lived tokens requested at publish time. The secure approach, the one the ecosystem recommends. The attacker's payload read /proc/<pid>/mem on the runner, pulled the OIDC token directly from the process memory of the GitHub Actions agent while the job ran, and authenticated npm publishes with it.

The result: packages built from a poisoned cache, authenticated with a legitimately issued OIDC token, signed with valid SLSA provenance. Every layer of the trust stack said yes.

What nobody's saying yet

The postmortems and the security coverage have focused, reasonably, on the attack chain itself. Pwn Requests have public documentation and years of CVEs behind them. Cache isolation is a known best practice. OIDC scoping is something teams get wrong constantly. The fixes are clear and worth doing.

The less-discussed part is what the incident asks us to update about SLSA provenance specifically.

Provenance attestation answers one question well: did this package build from the expected commit in the expected workflow? It answers a different question poorly: did the build environment arrive clean? Those are two questions we have been treating as one.

A pipeline can carry poisoned state before the build starts. That poison leaves no trace in the source commit or the workflow definition. The attestation records the pipeline configuration accurately and still says nothing about whether the cache state restored before that pipeline was trustworthy.

This does not make SLSA provenance useless. A package without provenance is harder to trust than one with it. The signal still matters. But the signal now has a documented attack surface, which means treating it as a final answer rather than a useful input is the mistake.

What would make provenance stronger from here: attestation of the cache restore, not just the build. A record of what state the runner pulled before execution began. GitHub could surface whether a job restored cache from a lower-trust workflow context. The SLSA spec could add a field for environment provenance separate from source provenance. Neither of those exists yet.

How the payload moved

The breach payload deserves a brief mention because it describes what was at stake for teams that installed during the window.

The 2.3 MB obfuscated file named router_init.js harvested GitHub tokens, AWS keys, Vault tokens, Kubernetes service accounts, SSH keys, and GCP credentials. It exfiltrated over a P2P messenger network. It then used any stolen publish-capable token to republish itself to every other package the victim could write to, which is how it spread from 42 initial packages to over 170 total. Secondary victims included @mistralai/mistralai and 40-plus @uipath packages.

The payload also installed persistence mechanisms in .claude/settings.json hooks, VS Code task injection, and a systemd monitoring service. If a stolen GitHub token was later revoked while the daemon ran, the payload wiped the home directory.

The campaign was attributed to a threat group called TeamPCP, linked to prior SAP, Checkmarx, and Trivy compromises.

If your CI ran during the window from 19:20 to 23:55 UTC on May 11 and you had @tanstack/react-router or @tanstack/start in your dependency tree, treat the build environment as potentially compromised. Rotate credentials that were present. Check for the gh-token-monitor systemd service and VS Code task injection before you rotate, because the wiper triggers on revocation while the daemon runs.

TanStack did the right thing

The TanStack team published a detailed timeline, named the advisory, credited the researcher who caught it, and documented what their internal detection missed. That is not a given. Most supply chain incidents at this scale get a vague security notice, a version bump, and a changelog entry that says "security fix."

A full postmortem is harder to write. It requires naming the specific workflow that failed, describing exactly which configuration was wrong, and putting in writing that internal alerting did not catch what an external researcher caught in minutes. The teams that publish postmortems like this make the ecosystem better. Worth acknowledging, and worth emulating.

Back to the Hacker News thread

We closed the three terminal windows a little before midnight. Our installs were fine. Our CI had not run during the window.

But we spent another 30 minutes looking at our own GitHub Actions workflows before going to sleep. Specifically the ones that use pull_request_target. We found one. That workflow did not check out fork code, so the specific Pwn Request vector was absent. The workflow ran with more permissions than the job needed.

We narrowed the permissions. We added a note in the workflow file explaining why.

The thing we kept thinking about, working through the YAML, is that we had treated SLSA provenance as a signal we could eventually rely on without examining what it actually attests. We knew the theory. We had read the spec summary. We had nodded along to conference talks where someone said "and then we add provenance attestation" as the slide transition to "and now we're secure."

May 11 changed that. Not catastrophically. Not in a way that breaks the provenance model or makes it wrong to adopt. But in the way that anything changes when it gets a real adversarial test for the first time.

The attestation was true. The pipeline was not clean. Those two facts coexisted inside the same package, on the registry, for four hours and thirty-five minutes, while 12.7 million weekly downloaders went about their Sunday.

That is the gap the supply-chain world gets to close now.

Valid SLSA provenance means the build ran in the expected place. That tells you nothing about whether the pipeline arrived clean.

Source: medium.

More from GLINR Studios.