The Call is Coming from Inside the House: Anatomy of the TanStack Cache Poisoning Attack
Supply chain attacks have evolved. Hackers are no longer just stealing npm passwords; they are hijacking GitHub Actions and poisoning CI/CD caches. Here is exactly how the TanStack breach happened.
For years, the standard advice for keeping your web application secure was simple, use strong passwords, enforce 2FA on your npm accounts, and be careful not to misspell your dependencies.
The recent attack on the TanStack ecosystem proved that this advice is no longer enough.
Supply chain attacks have evolved into something much more sophisticated, and much more terrifying. Attackers aren’t just trying to pick the lock on the front door any more, they are slipping through the air vents and poisoning the water supply.
Here is the technical breakdown of how one of the most popular React ecosystems was compromised, and why your GitHub Actions pipeline might be a massive security risk.
The Old Way: Typo-Squatting and Stolen Passwords
Historically, supply chain attacks were blunt instruments. An attacker would phish an open-source maintainer, steal their npm password, and manually push a malicious version of a popular package.
These attacks are devastating, but they are getting harder to pull off thanks to mandatory 2FA on npm. The attackers had to get smarter.
The New Way: The “Mini Shai-Hulud” Attack
In the TanStack breach, the attacker didn’t steal a single password. Instead, they weaponised TanStack’s own automated infrastructure against them.
Here is the step-by-step anatomy of the exploit:
Step 1: The Trojan Horse PR
The attacker created a fake GitHub account (cleverly disguised as an Anthropic AI bot) and opened a Pull Request (PR) against the TanStack/router repository.
This is where the critical flaw lay, the repository was using a GitHub Actions trigger called pull_request_target. This specific trigger is highly dangerous because it runs the workflow with elevated, “base repository” privileges, even if the code is coming from an untrusted, external fork.
Step 2: Poisoning the Well
When the automated CI/CD pipeline checked out the malicious code, the script didn’t try to immediately steal secrets. That would have tripped alarms.
Instead, it quietly executed a cache poisoning attack. It manipulated and overwrote a 1.1 GB cache file that the official release pipeline relied on to build the application.
Step 3: The Inside Job
The trap was set. Eight hours later, a legitimate TanStack maintainer pushed a routine, valid update.
The official release pipeline booted up. It pulled the poisoned cache (which contained the malicious payload), built the package, and published it directly to npm.
Because the package was built by the official pipeline using valid credentials, it bypassed 2FA entirely. In fact, because it came from the trusted automated system, the malware even carried valid cryptographic signatures (SLSA provenance) proving it was secure.
The call was quite literally coming from inside the house.
The Takeaway for System Architects
If you are building custom web applications, SaaS products, or utilising CMS architectures, this attack is a massive wake-up call.
We can no longer just audit our code, we must audit the machines that build our code.
How to harden your pipelines today:
-
Audit Your Triggers: Never use the
pull_request_targettrigger in GitHub Actions unless you explicitly understand how it handles untrusted code from forks. Stick to the standardpull_requesttrigger where possible. -
Isolate Your Caches: Ensure that untrusted PRs cannot write to the same caches that your production release pipelines read from.
-
Monitor your dependency tree: Utilising tools like Socket or Snyk to actively monitor your dependency tree for anomalous behaviour.
-
Enforce a Quarantine Period Natively (Package Age Gating)
The recent TanStack and Axios attacks relied on a “publish and pray” strategy, hoping CI/CD pipelines would automatically pull the malicious updates before the security community noticed.
You can completely neutralise this threat using a single line of configuration. Thanks to recent updates, all major package managers now natively support “Release Age” gating. By setting a rule in your configuration files, you can force your pipeline to reject any dependency version published within a specific window (e.g., the last 7 days).
How to set it up today:
For npm (v11.10.0+): Add min-release-age=7 to your .npmrc.
For Bun: Add minimumReleaseAge = 10080 to your bunfig.toml.
For pnpm: Add minimumReleaseAge: 10080 to your pnpm-workspace.yaml.
Never be the first person to install a new update. By enforcing a 7-day quarantine, you let the rest of the world find the exploits first. It is the highest-leverage, lowest-effort defence mechanism in modern web architecture.
Security is an Architectural Choice
Off-the-shelf platforms and bloated templates often hide these supply-chain vulnerabilities deep within layers of third-party plugins.
When you engineer custom architecture, you take back control of your supply chain. If you are a business on the Wirral running a web application, and you want to ensure your infrastructure is resilient, secure, and lightning-fast, let’s talk architecture.