# The Package Was Gone in Hours. The Stolen Token Lasted Months.

> Open-source libraries are a threat vector because install runs code on hosts that already hold tokens. TanStack's 11 May npm compromise lasted minutes on the registry and months in victim CI. CrowdSec learned in September that a May token had read private GitHub. Provenance does not close that gap. Scope the token. Hunt the clone.

Source: https://hackerlogs.com/blog/tanstack-token-lasted-months
Published: 2026-09-18

## Key takeaways

- TanStack's 11 May publish window was six minutes. StepSecurity filed in 26 minutes. npm pulled the last tarball in under five hours. That is the package clock.
- CrowdSec confirmed on 17 September that a May CI token had already read private GitHub. Four months after TanStack's all-clear. That is the impact clock.
- OIDC trusted publishing and SLSA provenance both held. The malware still published under TanStack's real identity. Provenance is not innocence.
- The payload harvested cloud credentials, GitHub tokens, and SSH keys at install time, then tried to republish from any maintainer it reached.
- Protect by splitting install from secrets, disabling lifecycle scripts in CI, refusing cache restore on publish jobs, and hunting clones after every advisory.

The part that should bother you is not that TanStack got hit. TanStack is one of the better-run libraries in JavaScript. The part that should bother you is the two clocks.

On [11 May](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem), 84 malicious versions across 42 `@tanstack/*` packages sat on npm for minutes. An outside researcher filed in 26 minutes. The last tarball was gone the same evening. On 15 May, TanStack posted an all-clear.

On [17 September](https://www.crowdsec.net/blog/crowdsec-statement-source-code-exposure), CrowdSec confirmed that a May CI token had already read its private GitHub tree. Fuites Infos had called the day before. Four months after the package clock stopped, the impact clock was still running.

That is why open-source projects are a threat vector now. You do not have to get hacked. You have to install a library you already trust, on a runner that can also clone private git. Sometimes it takes months to learn the exploit window was open.

  Trusted package, install-time theft, months before a victim is told. High because the token outlived the advisory, not because TanStack stayed dirty.

The maintainer page, 15 May. Not a diagram.

<img src="/tanstack-postmortem-source.png" alt="TanStack's public postmortem: the all-clear banner of 15 May, Router and Start as the only affected repo, and every current version marked safe to install." width="1200" height="615" />

The vendor page that closed the other clock, 17 September.

<img src="/crowdsec-statement-source.png" alt="CrowdSec's 17 September official statement: informed on 16 September of a May GitHub leak, private SaaS and AWS code in scope, public Security Engine out." width="1200" height="615" />

## Why a good library is now a door.

Attackers used to need your network. Then they needed a maintainer password. Now they need a workflow that already publishes for you, and a cache that already trusts a pull request.

Open source concentrates that trust. A popular library is installed by CI, by laptops, and by other libraries. Install is not a download. It is code execution on a host that already holds cloud metadata, GitHub tokens, npm credentials, and SSH keys. TanStack's own advisory says any environment that installed an affected version on 11 May should be treated as compromised.

The economics flipped. One six-minute publish against a loved namespace beats a year of phishing developers one at a time. The worm in this campaign, tracked as Mini Shai-Hulud, was built to steal those credentials and republish from the next maintainer it reached. Mistral's [SDK advisory](https://docs.mistral.ai/resources/security-advisories/MAI-2026-002) is the hop in the same week. CrowdSec is the hop you heard about in September.

> Popularity is the prize. TanStack Query, Table, and Form were untouched. Router and Start were not. The attacker did not need a shady package. They needed a namespace developers already allow to run in CI.

This is not a new class. xz-utils sat in Linux compression until a Microsoft engineer noticed SSH was 500 milliseconds slow. Shai-Hulud ate npm in 2025 by turning stolen tokens into more packages. The 2026 change is that the publish itself can look legitimate. OIDC trusted publishing, 2FA, and SLSA Build Level 3 provenance all held for TanStack. Snyk called it the first npm worm with valid SLSA attestations. The badge said the tarball came from `TanStack/router` on `main`. It did.

## TanStack is the cleanest example.

Nobody phished Tanner Linsley. Nobody stole an npm token. The [hardening note](https://tanstack.com/blog/incident-followup) is blunt: the project's own pipeline minted the publish credential and handed it over.

Three known classes, chained. A `pull_request_target` workflow checked out a fork pull request and ran it with base-repo cache write. That is the Pwn Request pattern GitHub's security lab has documented for years. The job poisoned the shared pnpm store under a key the release workflow would later restore. When a legitimate merge to main ran release, attacker binaries were already on disk. They pulled the short-lived OIDC token out of the Actions runner process and posted straight to the registry. The intended Publish Packages step never ran. Tests failed. The malware published anyway.

The first public write-up was issue #7383, opened at 19:46 UTC by ashishkurmi at StepSecurity, 26 minutes after the first publish. Socket called. TanStack deprecated the full 84 versions by 21:03. npm removed tarballs between 22:13 and 23:55. CISA put [CVE-2026-45321](https://nvd.nist.gov/vuln/detail/CVE-2026-45321) on the Known Exploited Vulnerabilities catalog on 27 May, due 10 June. CVSS 9.6.

That is excellent incident response for a package. It is also why people close the ticket.

> TanStack's all-clear means the registry is clean. It does not mean your May runner is clean. The payload's job was to leave with whatever the install host could see.

Downstream is where the months hide. [OpenAI](https://openai.com/index/our-response-to-the-tanstack-npm-supply-chain-attack/) said two employee devices were hit. Limited credential material left a subset of repos those people could read, including signing certificates. No customer data, no production, no rewritten software, per OpenAI. The company was already rolling out `minimumReleaseAge`. The two devices did not have it yet.

Mistral said an affected developer device published broken npm SDK versions and one working PyPI dropper. Hosted services and user data, per Mistral, were out of scope. Investigation closed 14 May.

[CrowdSec](/blog/crowdsec-tanstack-source-leak) is the late confirmation. Private SaaS, AWS routines, connectors, automations. Public Security Engine out of scope. No customer data, per the 17 September statement. The likely hop is a TanStack component in May that extracted a CI token with private-repo read. Same class, they said, as Mistral.

Three well-resourced shops. One library. Two of them said so in May. One needed a September phone call.

## Two clocks, only one you see.

The package clock is public. Advisories, deprecations, KEV listings, lockfile diffs. You can watch it.

The impact clock is private. A stolen GitHub token does not page you when it clones. A cloud key does not announce that it listed buckets. Session's file-upload network, which this payload used, is end-to-end encrypted. TanStack's postmortem says blocking by IP or domain is the only network mitigation. There is no attacker C2 dashboard for your SOC to stumble on.

That is why the unknown window stretches. You patched. You rotated the npm token you never lost. You did not hunt May clones from the runner that also held `contents: read` on the private org. CrowdSec's access window was a short stretch in May. The notice was September. Those are different facts.

> A lockfile stops the next surprise resolve. It does not unscrew an install that already ran. It also does not help if CI still has private-git read on the same job that runs install. The next loved library will publish for six minutes too. Pinning without token scope just records which malware you ran.

The harder part, the part TanStack itself called out, is that the project had no internal alerting on its own publishes. Detection was external. Downstream victims had the same shape: they learned from researchers, from journalists, or from a leak market. If your only signal is "a CVE exists," you are reading the package clock.

## What this is not.

It is not a reason to stop using TanStack. Query and the rest of the family were never in the 84 versions. Router and Start were cleaned the same night. The current registry, per the 15 May all-clear, is safe to install.

It is not a September intrusion at CrowdSec. September is the notice. May is the access. Do not file a fresh ticket because the headline is new. File the May CI token.

It is not proof that OIDC trusted publishing was a mistake. Long-lived npm tokens on laptops are still worse. TanStack's audit trail existed because publishes were bound to workflow runs. The hole was `id-token: write` on a runner that also restored an untrusted cache. Endor Labs' write-up is the same sentence: isolate the publish job, or the short-lived token is just a faster way to look official.

It is also not the [RubyGems agent flood](/blog/openai-rubygems-gemstuffer). Same month, different board. That registry was used as storage. This registry was used as a trusted execution path into other companies.

## How to protect against it.

You will not read every tarball. You can stop treating install as a read-only step.

1. Split the jobs. The runner that runs `npm ci` or `pnpm install` should not hold a GitHub token with private-repo read, cloud role credentials, or npm publish rights. CrowdSec's hop was a CI token that could read the private tree from the same place a TanStack install ran. If those are two jobs, a six-minute worm steals a useless identity.
2. Turn off install-time scripts in CI. Lifecycle hooks are how this payload ran. `ignore-scripts` is a blunt control. Native addons that truly need a build can be allowlisted. Everything else is surprise code execution.
3. Age the registry. OpenAI said `minimumReleaseAge` would have kept the two employee devices off the bad versions. pnpm 11's install cooldown is the same idea. A package that is 20 minutes old is not a package you need tonight.
4. Do not restore Actions cache on a publish job. TanStack disabled the pnpm cache in release and pulled `pull_request_target` out of CI. If a fork pull request can write a cache key that `main` will later read, you have already crossed the trust boundary. Pin third-party actions to commit SHAs while you are there.
5. Put `id-token: write` on one job, behind an environment approval, with no cache restore and no fork code. Workflow-level write means every step can mint a publish token. That is what "trusted publishing" actually trusts: the runner, not the maintainer.
6. Hunt after the advisory, not only during it. For any host that installed `@tanstack/*` on 11 May: rotate AWS, GCP, Kubernetes, Vault, GitHub, npm, and SSH material it could reach. Then search that month's CI logs for unexpected clones of private GitHub, and for egress to `filev2.getsession.org` and the `seed*.getsession.org` hosts. Four months is long enough for the useful code to move. It is also long enough to miss a second use of the same token.
7. Treat workflow files as privileged code. CODEOWNERS on `.github`, a static check such as zizmor on every pull request, and no `pull_request_target` that checks out the fork. TanStack is adding those because the pattern had been sitting in a working pipeline. Yours is too.

> Ask whether the job that installs from the public internet can also read private git, mint an OIDC publish token, or reach cloud metadata. If the answer is yes, the next TanStack is already in your org. You will hear about it when someone outside calls.

The package clock will keep looking fast. Researchers are good at this now. The impact clock is the one that ate CrowdSec's summer. Scope the token so a loved library cannot spend it. Then read May as if someone already cloned you, because in at least one security company's private git, they had.

## Sources

- [Postmortem: TanStack npm supply-chain compromise](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem) (2026-05-15)
- [Hardening TanStack After the npm Compromise](https://tanstack.com/blog/incident-followup) (2026-05-15)
- [Malware in 42 @tanstack/* packages exfiltrates cloud credentials, GitHub tokens, and SSH keys](https://github.com/TanStack/router/security/advisories/GHSA-g7cv-rxg3-hmpx) (2026-05-11)
- [CrowdSec Statement: Source Code Exposure in May 2026](https://www.crowdsec.net/blog/crowdsec-statement-source-code-exposure) (2026-09-17)
- [TanStack supply chain attack affecting Mistral SDK packages](https://docs.mistral.ai/resources/security-advisories/MAI-2026-002) (2026-05-14)
- [Our response to the TanStack npm supply chain attack](https://openai.com/index/our-response-to-the-tanstack-npm-supply-chain-attack/) (2026-05-13)
- [CVE-2026-45321](https://nvd.nist.gov/vuln/detail/CVE-2026-45321) (2026-05-27)
- [How a Misconfigured CI Workflow Became an npm Supply-Chain Compromise](https://www.endorlabs.com/learn/how-a-misconfigured-ci-workflow-became-an-npm-supply-chain-compromise) (2026-05-12)
