Table of Contents
The Full Picture of the TanStack npm Supply-Chain Compromise
The TanStack npm supply-chain compromise that occurred on May 11, 2026 was not merely a case of malware being inserted into a famous package. More precisely, it was an incident in which open-source publishing infrastructure, CI/CD, npm Trusted Publishing, the GitHub Actions cache, SLSA provenance, developer workstations, and AI coding tools were connected into one attack path.
According to TanStack's official postmortem, between 19:20 and 19:26 UTC on May 11, 2026, the attacker published 84 malicious versions across 42 @tanstack npm packages. In Japan, that corresponds roughly to 4:20 to 4:26 a.m. on May 12. In principle, two malicious versions were published for each affected package. The most visible cluster was around Router and Start packages that developers are likely to touch directly, including @tanstack/react-router, @tanstack/vue-router, @tanstack/solid-router, @tanstack/router-core, @tanstack/react-start, and @tanstack/router-plugin.
The important point is that the attacker did not steal a TanStack maintainer's npm token and publish directly. TanStack releases were not performed by a developer running npm publish from a laptop. They were published through the legitimate release workflow on GitHub Actions. TanStack was also using npm's OIDC Trusted Publishing, so long-lived npm publish tokens were not sitting in CI secrets or on developer machines. That has been the recommended direction for safer open-source publishing in the mid-2020s, and the design philosophy itself was not wrong.
And yet the attack still worked. Instead of stealing a token, the attacker chose a route where malicious code ran inside the legitimate workflow at the exact moment the legitimate workflow could mint a token. In other words, they did not steal the key. They slipped in front of the machine that creates the key.
Why TanStack Was Targeted
TanStack is a family of libraries widely used by frontend developers working with React, Vue, Solid, and related ecosystems. It includes projects such as Router, Query, Table, Form, and Virtual. Router-related packages are especially attractive to an attacker because they touch application routing, builds, server-side rendering, and developer tooling, and are frequently installed in both local development environments and CI. Socket, Snyk, and other researchers observed that @tanstack/react-router alone had more than ten million weekly downloads, making it an efficient entry point for a supply-chain attacker.
TanStack, however, was not the only destination. When reports from StepSecurity, Aikido, SafeDep, Snyk, Socket, Wiz, and others are compared, this attack appears as part of a self-propagating campaign known as Mini Shai-Hulud, with TanStack as one of the most visible starting points. The numbers vary by reporting time, but Aikido reported 373 malicious package-version entries across 169 npm package names, while Snyk's vulnerability database listed roughly 172 entries including TanStack-related packages and additional packages. Affected ecosystems included Mistral AI, UiPath, OpenSearch, Guardrails AI, Squawk, TallyUI, and DraftLab.
The variation in these numbers does not mean the reporting is useless. In a worm-style campaign, the impact expands while investigators are still counting. Deletion, quarantine, deprecation, republishing, and confirmation of secondary infections happen at the same time. For that reason, the question of how many packages were compromised depends on when the count was taken, which registries were included, and what detection criteria were applied. The firm number for TanStack itself is the official 42 packages and 84 versions. Broader ecosystem totals should be treated as provisional and moving.
Stage One: The Invisible Pull Request
The attack appears to begin suddenly on the npm registry, but the preparation started the previous day. According to TanStack's timeline, the attacker created a fork of TanStack/router at 17:16 UTC on May 10, 2026. The fork was renamed zblgg/configuration, a name that made it less obvious in ordinary fork listings. At 23:29 on May 10, the attacker added a roughly 30,000-line bundled JavaScript payload at packages/history/vite_setup.mjs, using the fabricated commit author name claude.
At 10:49 on May 11, the attacker opened a PR against TanStack/router itself. The title was "WIP: simplify history build." At first glance, it looked like a work-in-progress pull request to simplify a history-related build. The critical mechanism here was GitHub Actions pull_request_target.
pull_request_target is a GitHub Actions event that runs in the context of the base repository even when the PR comes from a fork. It is useful for tasks that need base-repository permissions, such as commenting, labeling, or lightweight checks. But if the workflow checks out untrusted PR code and then builds or installs it, the pattern becomes dangerous. GitHub Security Lab warned about this class of issue in 2021 under the name "Pwn Request." In 2024, security researcher Adnan Khan showed that GitHub Actions cache poisoning could allow lateral movement from apparently low-privilege workflows into higher-privilege workflows.
TanStack's bundle-size.yml crossed exactly that boundary. The workflow ran on pull_request_target, checked out the PR merge ref, executed pnpm install, and ran the bundle-size build. TanStack's intent was to isolate the job as a read-only check, but saving through actions/cache was not fully blocked by ordinary GITHUB_TOKEN permissions settings. The attacker used that behavior to save a malicious pnpm store into the base repository's cache.
Stage Two: The Cache Becomes a Time Bomb
For developers, a cache is usually a friend. It avoids downloading dependencies again and again and reduces build time. In a large monorepo, removing caches can make CI slower and more expensive. In this incident, that same convenience became a time bomb.
The malicious vite_setup.mjs did not immediately publish to npm. Instead, it precomputed the pnpm store cache key that the legitimate release.yml workflow would later use, then saved malicious files under a key beginning with Linux-pnpm-store. According to TanStack, the cache entry was about 1.1 GB and was saved at 11:29 UTC on May 11, 2026. The attacker then force-pushed the PR back to match current main, making the visible PR look like a zero-file no-op, closed the PR, and deleted the branch. Nothing appeared to be happening on the surface, but poison remained inside the base repository's cache.
That invisible residue detonated roughly eight hours later. Around 19:15, an ordinary maintainer change landed on main, triggering release.yml. That workflow was the legitimate release pipeline and had id-token: write permission, which npm OIDC Trusted Publishing needs. During the Setup Tools step, the workflow restored the malicious pnpm store. The attacker did not need a maintainer to merge the malicious PR. They only needed to wait for a legitimate change to main.
This is what made the incident unusual. The malware was not merged into TanStack's main branch. Reviewed source code was not directly altered. The compromised layer was the cache shared between workflows. Code review, signed commits, and a strong review culture can still miss this layer if the trust boundary between workflows and caches is not modeled.
Stage Three: Extracting the OIDC Token from Memory
release.yml could issue an OIDC token for npm publishing. OIDC, or OpenID Connect, is the mechanism by which GitHub Actions proves to npm that a workflow run comes from a particular repository and execution context, and npm then grants a short-lived publish capability. This avoids storing long-lived npm tokens and is stronger against traditional token theft.
However, the attacker had code execution inside the workflow that could mint that short-lived token. The malicious binary searched for the GitHub Actions Runner.Worker process, read process memory through the Linux proc filesystem, and extracted OIDC-related tokens. TanStack explained that this technique resembled the memory-extraction method used in the March 2025 tj-actions/changed-files compromise. The attacker did not invent a completely new technique; they combined known techniques in a new operational chain.
The extracted token was then used to POST directly to registry.npmjs.org, bypassing the workflow's formal Publish Packages step. In fact, TanStack's intended publish step had been skipped because tests failed. Even so, from npm's perspective the publish was authenticated through a legitimate OIDC trusted-publisher binding. That is the defensive blind spot: the registry could confirm that the request came from the right workflow identity, but it could not fully distinguish which process inside that workflow made the request and with what intent.
What Was Inside the Malicious Packages
The malicious TanStack packages contained two major changes. First, package.json received an optionalDependencies entry for @tanstack/setup. This was not a normal package from the npm registry. It pointed to the GitHub dependency github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c. It looked as if it referenced a commit in the official TanStack repository, but in reality it resolved to an orphan commit within the fork network. Because GitHub shares objects across forks, a commit pushed to an attacker's fork can sometimes be reachable through a URL that includes the parent repository name. That behavior was useful for social camouflage.
Second, a roughly 2.3 MB obfuscated JavaScript file named router_init.js was added to the package root. This was not normal build output. It was a serious credential-stealing tool using string-array rotation, control-flow flattening, encrypted payload components such as AES-256-GCM, and Bun runtime dependencies. Socket said its AI scanner detected all 84 artifacts within six minutes after publication, but the code was not something a human could reasonably audit by sight.
The @tanstack/setup dependency contained a prepare lifecycle script. npm runs prepare when installing Git dependencies, so the malicious behavior could start during npm install, pnpm install, or yarn install. Because the dependency was optional, failure could be swallowed by the install process. The attacker deliberately used exit 1 at the end, causing the optional dependency to appear to fail after the payload had already run. If a defender only looked at node_modules after installation and saw nothing obvious, the infection could be missed.
What the Malware Tried to Steal
The malware was not primarily interested in ordinary browser history. Its main targets were developer machines, CI/CD runners, cloud environments, and package publishing rights. GitHub Advisory, Snyk, and StepSecurity analyses describe payload behavior that searched AWS instance metadata, AWS Secrets Manager, GCP metadata, Kubernetes service account tokens, HashiCorp Vault tokens, npm tokens, GitHub tokens, gh CLI configuration, .git-credentials, and SSH private keys.
One especially dangerous detail was how carefully the malware targeted cloud-native environments. For AWS, it implemented IMDSv2 session-token acquisition, not only older IMDSv1 access. That means environments that merely blocked the naive metadata approach could still be exposed depending on configuration. In Kubernetes, it looked under /var/run/secrets/kubernetes.io/serviceaccount, and for Vault it targeted service names such as vault.svc.cluster.local. If a Kubernetes service account had excessive permissions, a simple npm install could become a path to cluster-wide secret exposure.
The target surface also extended into a developer's daily work environment. Reports mention Claude Code session history, VS Code settings, crypto wallets, VPN configurations, Signal, Slack, Discord, Telegram, and shell history. Shell history is attractive because many developers temporarily paste API keys or tokens into command lines. A supply-chain attack is therefore not just a dependency problem. It is also a problem of everyday developer habits.
Exfiltration and Persistence
Stolen data was mainly sent through the Session/Oxen file-upload network and GitHub GraphQL API. Session is a privacy-oriented messaging system, not an obvious attacker-controlled C2 server. That makes simple IP blocking less reliable. Snyk recommended practical network monitoring or blocking at the DNS level for endpoints such as filev2.getsession.org and seed1, seed2, and seed3.getsession.org.
Another route was a GitHub-based dead drop. The attacker used the GitHub GraphQL API createCommitOnBranch mutation to commit encrypted stolen data into attacker-controlled repositories. Commit authorship was spoofed as claude@users.noreply.github.com, commit messages resembled chore: update dependencies, and branch names looked like Dependabot-style names containing terms from Dune. The campaign name Shai-Hulud itself also comes from the giant sandworm in Dune. The attacker was not only hiding technically; they gave the campaign a recognizable theme. That signature was not harmless theater. It was a way to be recognized by researchers and also a detection clue.
Persistence was also deliberate. At startup, the payload daemonized itself, closed standard output, and made itself less visible during npm install. It wrote hooks into .claude/settings.json so it could run again when Claude Code sessions started. It used .vscode/tasks.json to run setup.mjs when a folder was opened in VS Code. Researchers also reported possible Linux systemd user services and macOS LaunchAgents under names such as gh-token-monitor. In practical terms, npm uninstall is not enough. Even if the original package is removed, hooks left in developer tool settings can continue reinfection or exfiltration.
The Fear of a Self-Propagating Worm
What separates Mini Shai-Hulud from an ordinary infostealer is that it tries to use stolen credentials to spread into the next package. The payload searches for npm tokens and, if it finds tokens that can bypass 2FA for publishing, it can enumerate packages maintained by the victim and inject the same malware. Alternatively, it can abuse CI environments with OIDC publishing rights and publish the next malicious version through what looks like a legitimate release path.
The mechanism resembles infection. A compromised developer environment is not merely an endpoint; it can become the next publisher. For example, if a company's CI installed a malicious TanStack package and that same CI had permission to publish the company's npm packages, the worm could inject the malicious optional dependency and router_init.js into those packages too. A later user would install them, and the chain could continue.
In the initial Shai-Hulud wave of September 2025, hundreds of npm packages and many GitHub repositories were affected. A later November 2025 wave drew attention because it involved packages with more than 100 million monthly downloads. In April 2026, Mini Shai-Hulud affected SAP, Intercom, Lightning, and others, and persistence through AI coding tools became visible. In the May 2026 wave, the TanStack incident added legitimate CI and provenance attestations to the chain. Each wave has turned one of defenders' comfort points into part of the attack path.
The Misleading Claim That SLSA or Provenance Was Broken
One of the easiest phrases to get wrong is "SLSA was broken." More accurately, SLSA provenance and npm provenance worked according to their design, but they were not sufficient to decide whether the package was safe.
npm provenance lets consumers verify where a package was built and by which publishing identity it was released. It uses mechanisms such as Sigstore short-lived certificates and transparency logs to make origin harder to forge. That is a meaningful improvement and is safer than leaving long-lived publish tokens in secrets. But provenance guarantees that a package came from a particular build environment. It does not guarantee that every process running inside that environment was benign.
The malicious TanStack packages were published from the legitimate GitHub Actions release runner, through the legitimate OIDC path, with legitimate provenance attached. That is precisely why the incident was hard. If a defender treats "signed," "provenance present," or "OIDC published" as the final safety decision, the malicious versions can be missed. For a general-audience technical essay, this is the largest lesson: origin proof is necessary, but it is not sufficient.
Who May Have Been Affected
TanStack said that any developer or CI environment that installed an affected version on May 11, 2026 should treat the install host as potentially compromised. In Japan, the main publish window was roughly 4:20 to 4:26 a.m. on May 12, but defenders should not look only at that six-minute window. There can be delays before malicious versions are removed, deprecated, or pulled from the registry, and lockfiles, CI caches, internal mirrors, and package proxies may preserve affected versions.
Examples of confirmed malicious TanStack versions include @tanstack/react-router, @tanstack/vue-router, @tanstack/solid-router, and @tanstack/router-core at 1.169.5 and 1.169.8. @tanstack/history was affected at 1.161.9 and 1.161.12, @tanstack/react-start at 1.167.68 and 1.167.71, and @tanstack/router-plugin at 1.167.38 and 1.167.41. Many affected packages sit around Router and Start. The complete list is in the GitHub Advisory and TanStack tracking information.
At the same time, TanStack treated @tanstack/query, @tanstack/table, @tanstack/form, @tanstack/virtual, @tanstack/store, and the @tanstack/start meta-package as confirmed clean families for this specific TanStack scope. The important caveat is to avoid confusing similar package names, Start subpackages, Router devtools, and SSR-related packages.
What to Check
First, check lockfiles. Search package-lock.json, pnpm-lock.yaml, and yarn.lock for affected @tanstack versions, @tanstack/setup, or github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c. Also check node_modules for router_init.js and package.json optionalDependencies entries for @tanstack/setup. If you inspect tarballs, do so in a way that extracts contents without running install scripts.
Second, check developer machines and CI runners for persistence. Inspect .claude/settings.json, .claude/setup.mjs, .claude/router_runtime.js, .vscode/tasks.json, and .vscode/setup.mjs for unexpected changes. On Linux, indicators may include ~/.config/systemd/user/gh-token-monitor.service. On macOS, indicators may include ~/Library/LaunchAgents/com.user.gh-token-monitor.plist. In GitHub repositories, look for unexpected commits by claude@users.noreply.github.com, messages such as chore: update dependencies, and Dependabot-style branches using Dune terminology.
Third, review network and workflow logs. Look for traffic during npm install or builds to filev2.getsession.org, seed1.getsession.org, api.masscan.cloud, git-tanstack.com, or payloads hosted on litter.catbox.moe. In GitHub Actions logs, look for unexpected Bun execution, tanstack_runner.js, router_init.js, python3 reading /proc memory, and unexpected npm publish activity.
Fourth, be careful about the order of credential response. In general, GitHub PATs, npm tokens, AWS, GCP, Azure, Vault, Kubernetes, SSH keys, and PyPI tokens reachable from the affected environment should be rotated. However, StepSecurity and Snyk warned that if a token has the description IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner, teams should isolate the machine and check persistence and destructive behavior before revoking blindly. The reason is that researchers observed designs where token revocation could trigger destructive routines. A disciplined response should separate network isolation, forensic preservation, persistence removal, and credential rotation.
Defensive Lessons for Organizations
The response to this incident cannot end with "update TanStack." First, organizations need to inventory pull_request_target workflows. A workflow that checks out untrusted PR code, runs npm install or a build, and touches base-repository caches or permissions is high risk. As GitHub Security Lab recommends, use pull_request when base privileges are not needed, and split privileged follow-up work into workflow_run or another separated workflow so untrusted code and privileged permissions do not meet.
Second, release workflow caches need review. Adnan Khan's 2024 research warned that cache poisoning could affect builds with SLSA and signed artifacts. This incident showed that warning in a major real-world case. After the incident, TanStack deleted affected workflow caches, disabled pnpm caching in the release pipeline, and pinned organization Actions to commit SHAs. Floating references such as tags or @main can become future supply-chain risk.
Third, OIDC permissions should be minimized. id-token: write should not be granted broadly at the workflow level. It should be limited to the specific job that publishes. npm Trusted Publisher configuration should also be limited to specific branches, workflow files, and, where useful, protected environments and approvals. Short-lived tokens are safer than long-lived tokens, but if a runner is compromised while the token is alive, the token can still be abused.
Fourth, monitor install-time behavior. Signatures and provenance are not enough. Defenders need to examine what packages execute in install, preinstall, postinstall, and prepare hooks; whether they suddenly download Bun or Python; and whether they contact cloud metadata endpoints, Vault, or the GitHub API. A release-age cooldown for newly published package versions can also help. Ecosystem features such as delayed adoption, internal package proxies, SCA, SBOMs, and runtime egress controls can reduce the blast radius of a zero-day malicious publish.
Fifth, do not turn developer machines into production secret stores. Claude Code, VS Code, gh CLI, npmrc files, SSH keys, shell history, and local Vault tokens can all be valuable to an attacker. In an era when AI coding tool configuration files can contain execution hooks, source code is not the only thing that needs auditing. The focus of supply-chain attacks in the early 2020s was often package names or maintainer accounts. This 2026 incident shows that the developer workspace itself is now a target.
The Essence of the Incident
The TanStack npm compromise is less a story of total defensive failure than a story of misplaced boundaries. 2FA was useful. OIDC without long-lived npm tokens was useful. Provenance was attached correctly. Yet separate trust boundaries, including pull_request_target, Actions cache, OIDC token issuance, and npm publishing, were connected by the attacker into one route.
For this class of attack, individual controls are not enough on their own. The important question is where untrusted code runs, what cache its output can leave behind, which later workflows can restore that cache, and at what moment the restored result touches publishing permissions or cloud permissions. A software supply chain is not only a bill of materials. It is an operational chain that includes review, CI, caches, registries, certificates, developer machines, and AI tools.
The attacker chose the least visible layer of that chain. The PR was closed, the diff disappeared, main looked clean, the package was published from legitimate CI, and provenance was present. Even so, installation environments could have credentials stolen and could become the next publishing path.
The lesson is not "do not use npm," "do not use GitHub Actions," or "do not use AI tools." The better lesson is that modern development infrastructure has become stronger through convenience and automation, and attackers can use that automation too. Even after adopting trusted mechanisms, teams must keep checking the assumptions those mechanisms rely on. The TanStack incident shows that, in the 2026 open-source supply chain, the intuition that "if it came through the official path, it is safe" needs to be updated.
References
- TanStack official postmortem
- TanStack hardening follow-up
- GitHub Advisory GHSA-g7cv-rxg3-hmpx
- Socket: TanStack npm Packages Compromised
- Socket: Mini Shai-Hulud campaign tracker
- Aikido: Mini Shai-Hulud is back
- Snyk: TanStack npm Packages Hit by Mini Shai-Hulud
- GitHub Security Lab: Preventing pwn requests
- Adnan Khan: GitHub Actions cache poisoning

NEW NOVEL 2026/08/01
Clouded Glass
Polishing is not about force.
Volume two of The World Became Slightly Farther Away.Five stories that can also be read as a starting point.
View on Amazon
Jijoden.com
Your life is worth writing.
There is a truer self you can tell only to AI.Gather fragments of memory into a single story.
Take a LookRelated Articles
Designing Web APIs on AWS in 2026: A Practical Architecture Guide to Auth, Performance, Security, and Cost
A deeply researched guide to designing Web APIs on AWS in 2026, covering internal, B2B, B2C, and agentic workloads; API Gateway, Lambda, Fargate, OIDC, RDS Proxy, asynchronous processing, 10,000-user scale, cost, and multi-cloud portability.
What is OIDC? How is it Different? Why is it "Amazing"? (Complete Guide with AWS×GitHub Examples)
Learn OIDC (OpenID Connect) from the ground up. Understand the differences from OAuth2.0 and SAML, with practical examples of AWS and GitHub implementations.
Your Home PC Is Becoming a Remote AI Agent Workstation
Using Claude Code Remote Control and Codex mobile access as reference points, this article explains how local development machines are becoming remotely supervised AI agent workstations.
From API Keys to Web Integration — A Hands‑on Guide to OpenAI, Anthropic Claude, and Amazon Bedrock
A practical guide for integrating generative AI APIs into real web apps. Covers key acquisition, auth, minimal code, pricing basics, safe Next.js patterns, and operations best practices.
Complete Guide to CORS (Cross-Origin Resource Sharing)
A comprehensive guide to CORS for frontend developers, covering error causes, solutions, same-origin policy, and preflight requests.