Build1 publisher3 min readPublished
A compromised TanStack pipeline signed 84 poisoned packages with valid provenance
Attestation proves a package came out of the pipeline it names. When the pipeline is the thing that was owned, the seal still verifies, and a gate that only checks origin passes all 84 versions pushed on May 11.
The Engineer · Build desk
What happened
- A dev.to post cataloguing May 2026's supply-chain attacks says someone compromised TanStack's CI on the 11th and pushed 84 poisoned package versions in six minutes.
- Those poisoned TanStack versions carried valid signing provenance pointing at the real build pipeline, so the attestation verified while the contents were malicious.
- On 14 May, three malicious versions of node-ipc, a library with ten million weekly downloads, shipped an identical credential-stealer.
- Days after that the @antv packages were hit, cascading down into a charting library a million projects depend on.
- Each of the packages ran its payload at install time and then tried to publish itself from the machine it had landed on.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- constraint Requiring provenance constrains who can publish and not what they publish, so a team reporting attestation coverage as a supply-chain metric has no measurement of package contents at all.
- decision Anyone whose builds allow dependency code to execute at install or build time now has a live decision to make about that default, because Go, Composer and Python wheels ship without it.
- cost The bill for all this falls on the consuming engineer: pinned SHAs, committed lockfiles, auditors wired into every build, and expiry dates on exceptions, none of which the registry does for you.
- exposure Credentials a build never needed are reachable by any dependency that gets to run code during install. That puts CI secret scope in the same review as the dependency tree.
Verifying provenance checks identity. It says an artifact came out of the workflow and the repository it names. According to a dev.to post by the developer who writes as phpboyscout, the TanStack versions pushed on May 11 2026 carried exactly that: real attestation, pointing at the real pipeline [2]. Whoever held the CI held the signing path with it. A gate configured to reject anything unattested would have let all 84 through [16].
"If the pipeline doing the signing is itself compromised, the signature is still perfectly valid. It just now certifies a lie," the author wrote [6]. Provenance answers where a package came from; safety of the contents is a separate question [8]. The same post calls a signature without diligence behind it "a tamper-proof seal on a poisoned jar" [7].
The payload does not wait to be imported. Installing runs it, and it then tries to publish itself from the machine it landed on [5].
Three ecosystems already run without that behaviour. Go runs no install or build hooks at all, PHP's Composer runs only your own project's scripts and refuses a dependency's, and Python's wheel format forbids install hooks [9]. Cargo's build.rs does the same job at build time instead of install time, and the author says the TrapDoor campaign used it to rifle through keystores on crates.io this year [10]. On Rust's position he is blunt: he calls it a smaller, better-policed target, which he says is a different thing [11].
Eighty-four versions in six minutes works out at fourteen a minute [15]. I would not expect a review step that needs a person to read a diff to fit inside that window. The controls that can are the ones that run unattended on every build.
Registries can sign, scan, attest origin and mandate 2FA on maintainers, and the post's argument is that none of it is a guarantee, because a maintainer gets phished, a CI token leaks, a trusted contributor turns, or a dependency four levels down changes hands [12]. Add CI that holds credentials the build never needed, and a dependency tree thousands of strangers deep [20].
What the post recommends is cheap and dull. It advises pinning CI actions to commit SHAs, so a moved tag cannot swap code under you, and committing lockfiles. Run cargo-audit, pip-audit, govulncheck, npm audit or OSV-Scanner on every build, the author says, gate the dependency tree, and give every exception an expiry date [13]. None of that inspects contents either; it narrows what an attacker can reach and caps how long an ignored finding survives.
The post does not say how TanStack's CI was taken over [18]. It is clear about the bind that leaves a consumer in: patch slowly and you are running known-vulnerable dependencies, patch the instant a release drops and you skip the bedding-in that might have caught a poisoned one [19]. "There's no setting on that dial that's safe, only trade-offs you have to actually think about," the author wrote [14].
What to watch
- Whether TanStack or the registry publishes how the CI was taken over, which the post does not cover.
- Whether npm or GitHub change what an attestation asserts, so verification says something about contents and not only build origin.
- Whether ecosystems that still run dependency install hooks add a way to switch them off by default, as Composer already does.