# TanStack npm Supply Chain Attack: Detect, Fix, and Recover

URL: https://blog.bokvi.com/blog/tanstack-npm-supply-chain-attack/
Published: 2026-05-13
Tags: security, javascript, supply-chain, tanstack, npm, ci-cd, incident-response

On **May 11, 2026**, attackers published **84 malicious versions across 42 `@tanstack/*` npm packages** between approximately **19:20 and 19:26 UTC**. The attack did not begin with a stolen npm password. Instead, the attacker abused a GitHub Actions chain involving a `pull_request_target` workflow, GitHub Actions cache poisoning, and extraction of an OIDC token from the Actions runner process. The malicious packages were published through TanStack's legitimate trusted-publisher identity, which made the incident especially dangerous. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

The payload executed during package installation and attempted to steal secrets from developer machines and CI environments, including cloud credentials, GitHub tokens, npm tokens, Kubernetes service-account tokens, Vault tokens, and SSH private keys. Any developer workstation or CI runner that installed an affected version on May 11, 2026 should be treated as potentially compromised. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

## Executive summary

This was a **supply-chain malware incident**, not a normal dependency vulnerability.

The important takeaways are:

- If you installed an affected package version, do **not** assume that removing the package is enough.
- The malware attempted credential theft and self-propagation.
- Some write-ups report persistence mechanisms on developer machines, including Claude Code hooks, VS Code task hooks, and a GitHub-token monitoring daemon.
- Rotate credentials **after** removing persistence, especially if the `gh-token-monitor` daemon is present.
- CI runners that installed affected versions should be rebuilt from clean images, not merely cleaned in place.

The official GitHub advisory tracks the issue as **CVE-2026-45321** and **GHSA-g7cv-rxg3-hmpx**, with critical severity. ([GitHub Advisory](https://github.com/advisories/GHSA-g7cv-rxg3-hmpx))

## What happened?

The attacker created a fork of the TanStack router repository and used a malicious pull request to poison GitHub Actions cache data. A legitimate TanStack release workflow later restored that poisoned cache. From there, attacker-controlled code ran inside the release workflow and extracted the GitHub Actions OIDC token from runner memory. The attacker then used that token to publish malicious npm package versions. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

TanStack's own postmortem describes three linked weaknesses:

1. A `pull_request_target` workflow that checked out and executed fork-controlled code.
2. GitHub Actions cache poisoning across the fork/base trust boundary.
3. Runtime extraction of a GitHub Actions OIDC token from the runner process. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

As a result, the malicious packages appeared to come from the legitimate publishing pipeline. Security researchers reported that the broader "Mini Shai-Hulud" campaign produced packages with valid-looking SLSA provenance, showing that provenance alone is not enough when the trusted build pipeline itself has been hijacked. ([StepSecurity](https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem))

## What did the malware do?

When a developer or CI environment installed an affected version, the package could execute an install-time payload. The key indicator inside `package.json` was an unexpected optional dependency:

```json
"optionalDependencies": {
  "@tanstack/setup": "github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c"
}
```

Alongside it, the package shipped an unexpected file at its root:

```text
router_init.js
```

The payload attempted to harvest credentials from locations such as:

- AWS metadata and Secrets Manager
- GCP metadata
- Kubernetes service-account tokens
- HashiCorp Vault tokens
- `~/.npmrc`
- GitHub tokens from environment variables, `gh` CLI config, and `.git-credentials`
- SSH private keys under `~/.ssh/`

It also attempted to exfiltrate data through Session/Oxen infrastructure and to self-propagate by enumerating npm packages maintained by the compromised user or environment. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

Security vendors also reported persistence behavior in developer tooling directories and a GitHub-token monitoring daemon on Linux and macOS. That daemon reportedly polled GitHub and could attempt destructive behavior if the monitored token was revoked, which is why the remediation order matters. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

## Affected TanStack packages

The official advisory lists 42 affected TanStack packages, each with two malicious versions and a patched follow-up version. Update to the patched version or later. ([GitHub Advisory](https://github.com/advisories/GHSA-g7cv-rxg3-hmpx))

| Package | Affected versions | Patched version |
|---|---:|---:|
| `@tanstack/arktype-adapter` | `1.166.12`, `1.166.15` | `1.166.16` |
| `@tanstack/eslint-plugin-router` | `1.161.9`, `1.161.12` | `1.161.13` |
| `@tanstack/eslint-plugin-start` | `0.0.4`, `0.0.7` | `0.0.8` |
| `@tanstack/history` | `1.161.9`, `1.161.12` | `1.161.13` |
| `@tanstack/nitro-v2-vite-plugin` | `1.154.12`, `1.154.15` | `1.154.16` |
| `@tanstack/react-router` | `1.169.5`, `1.169.8` | `1.169.9` |
| `@tanstack/react-router-devtools` | `1.166.16`, `1.166.19` | `1.166.20` |
| `@tanstack/react-router-ssr-query` | `1.166.15`, `1.166.18` | `1.166.19` |
| `@tanstack/react-start` | `1.167.68`, `1.167.71` | `1.167.72` |
| `@tanstack/react-start-client` | `1.166.51`, `1.166.54` | `1.166.55` |
| `@tanstack/react-start-rsc` | `0.0.47`, `0.0.50` | `0.0.51` |
| `@tanstack/react-start-server` | `1.166.55`, `1.166.58` | `1.166.59` |
| `@tanstack/router-cli` | `1.166.46`, `1.166.49` | `1.166.50` |
| `@tanstack/router-core` | `1.169.5`, `1.169.8` | `1.169.9` |
| `@tanstack/router-devtools` | `1.166.16`, `1.166.19` | `1.166.20` |
| `@tanstack/router-devtools-core` | `1.167.6`, `1.167.9` | `1.167.10` |
| `@tanstack/router-generator` | `1.166.45`, `1.166.48` | `1.166.49` |
| `@tanstack/router-plugin` | `1.167.38`, `1.167.41` | `1.167.42` |
| `@tanstack/router-ssr-query-core` | `1.168.3`, `1.168.6` | `1.168.7` |
| `@tanstack/router-utils` | `1.161.11`, `1.161.14` | `1.161.15` |
| `@tanstack/router-vite-plugin` | `1.166.53`, `1.166.56` | `1.166.57` |
| `@tanstack/solid-router` | `1.169.5`, `1.169.8` | `1.169.9` |
| `@tanstack/solid-router-devtools` | `1.166.16`, `1.166.19` | `1.166.20` |
| `@tanstack/solid-router-ssr-query` | `1.166.15`, `1.166.18` | `1.166.19` |
| `@tanstack/solid-start` | `1.167.65`, `1.167.68` | `1.167.69` |
| `@tanstack/solid-start-client` | `1.166.50`, `1.166.53` | `1.166.54` |
| `@tanstack/solid-start-server` | `1.166.54`, `1.166.57` | `1.166.58` |
| `@tanstack/start-client-core` | `1.168.5`, `1.168.8` | `1.168.9` |
| `@tanstack/start-fn-stubs` | `1.161.9`, `1.161.12` | `1.161.13` |
| `@tanstack/start-plugin-core` | `1.169.23`, `1.169.26` | `1.169.27` |
| `@tanstack/start-server-core` | `1.167.33`, `1.167.36` | `1.167.37` |
| `@tanstack/start-static-server-functions` | `1.166.44`, `1.166.47` | `1.166.48` |
| `@tanstack/start-storage-context` | `1.166.38`, `1.166.41` | `1.166.42` |
| `@tanstack/valibot-adapter` | `1.166.12`, `1.166.15` | `1.166.16` |
| `@tanstack/virtual-file-routes` | `1.161.10`, `1.161.13` | `1.161.14` |
| `@tanstack/vue-router` | `1.169.5`, `1.169.8` | `1.169.9` |
| `@tanstack/vue-router-devtools` | `1.166.16`, `1.166.19` | `1.166.20` |
| `@tanstack/vue-router-ssr-query` | `1.166.15`, `1.166.18` | `1.166.19` |
| `@tanstack/vue-start` | `1.167.61`, `1.167.64` | `1.167.65` |
| `@tanstack/vue-start-client` | `1.166.46`, `1.166.49` | `1.166.50` |
| `@tanstack/vue-start-server` | `1.166.50`, `1.166.53` | `1.166.54` |
| `@tanstack/zod-adapter` | `1.166.12`, `1.166.15` | `1.166.16` |

TanStack reported the following families as confirmed clean: `@tanstack/query*`, `@tanstack/table*`, `@tanstack/form*`, `@tanstack/virtual*`, `@tanstack/store`, and the `@tanstack/start` meta-package. Note that this does **not** include similarly named `@tanstack/start-*` packages, several of which were affected. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

## How to check whether you were exposed

The safest rule is this:

> If a developer machine or CI runner installed an affected version on May 11, 2026, treat the environment as compromised.

Do not run `npm install` just to test exposure. Instead, inspect lockfiles, existing `node_modules`, package tarballs, and system persistence locations.

### 1. Search your project for known malicious markers

From the root of each repository:

```bash
grep -R --line-number -E \
  '79ac49eedf774dd4b0cfa308722bc463cfe5885c|@tanstack/setup|router_init\.js|tanstack_runner\.js' \
  package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json node_modules 2>/dev/null
```

Any hit for the malicious git ref, `@tanstack/setup`, or `router_init.js` should be treated as a serious indicator.

### 2. Search for the payload file

Linux/macOS:

```bash
find . -name "router_init.js" -o -name "tanstack_runner.js"
```

If you find `router_init.js`, calculate its hash:

```bash
shasum -a 256 path/to/router_init.js
```

Known reported SHA-256 for `router_init.js`:

```text
ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c
```

Known reported SHA-256 for `tanstack_runner.js`:

```text
2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96
```

These hashes and filenames were published by security researchers as indicators of compromise. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

### 3. Inspect package tarballs without executing install scripts

To inspect a package version safely:

```bash
mkdir /tmp/tanstack-check
cd /tmp/tanstack-check

npm pack @tanstack/react-router@1.169.5
tar -xzf tanstack-react-router-*.tgz

grep -A5 "optionalDependencies" package/package.json
ls -la package/router_init.js
```

A malicious package contains the `@tanstack/setup` git dependency and the unexpected `router_init.js` payload file at the package root. The GitHub advisory specifically recommends `npm pack` inspection because it downloads the tarball without executing lifecycle scripts. ([GitHub Advisory](https://github.com/advisories/GHSA-g7cv-rxg3-hmpx))

### 4. Check for local persistence

Search your home directory and project directories for persistence files reported in the campaign:

```bash
find "$HOME" \
  -path "*/.claude/router_runtime.js" -o \
  -path "*/.claude/setup.mjs" -o \
  -path "*/.vscode/setup.mjs" -o \
  -path "*/.config/systemd/user/gh-token-monitor.service" -o \
  -path "*/.local/bin/gh-token-monitor.sh" -o \
  -path "*/Library/LaunchAgents/com.user.gh-token-monitor.plist" \
  2>/dev/null
```

Also inspect Claude Code and VS Code hook/task configuration:

```bash
grep -R --line-number -E 'router_runtime|setup\.mjs|gh-token-monitor' \
  "$HOME/.claude" .claude .vscode 2>/dev/null
```

Snyk and Wiz reported persistence artifacts including `.claude/router_runtime.js`, `.claude/setup.mjs`, `.vscode/setup.mjs`, Linux `gh-token-monitor` systemd user services, and macOS LaunchAgents. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

### 5. Check for suspicious GitHub workflow injection

Look for an unexpected workflow file:

```bash
ls -la .github/workflows/codeql_analysis.yml 2>/dev/null
```

If this file exists and your team did not add it intentionally, inspect it before deleting it:

```bash
sed -n '1,200p' .github/workflows/codeql_analysis.yml
```

Security researchers reported that the malware attempted to inject a GitHub Actions workflow named `codeql_analysis.yml` to exfiltrate secrets. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

### 6. Check Git history for suspicious dead-drop commits

```bash
git log --all --author="claude@users.noreply.github.com" --oneline
git branch -a | grep -i "dependabout"
```

The attacker used a fabricated `claude@users.noreply.github.com` identity and Dependabot-style branch names such as `dependabout/.../setup-formatter`. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

### 7. Check network and proxy logs

Search DNS, proxy, firewall, EDR, and CI network logs for:

```text
filev2.getsession.org
seed1.getsession.org
seed2.getsession.org
seed3.getsession.org
git-tanstack.com
api.masscan.cloud
litter.catbox.moe/h8nc9u.js
litter.catbox.moe/7rrc6l.mjs
```

TanStack, GitHub, Snyk, and Wiz reported these as exfiltration, C2, or second-stage payload indicators. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

## How to fix an affected workstation

### Step 1: Isolate the machine

Disconnect the machine from untrusted networks or place it in a restricted network segment. Preserve logs if this is a company device.

Do not immediately revoke GitHub tokens if you found `gh-token-monitor`. First remove the persistence mechanism. Researchers reported a token-monitoring daemon that could attempt destructive behavior when a monitored token was revoked. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

### Step 2: Stop and remove the token-monitor persistence

Linux:

```bash
systemctl --user stop gh-token-monitor.service 2>/dev/null
systemctl --user disable gh-token-monitor.service 2>/dev/null

rm -f "$HOME/.config/systemd/user/gh-token-monitor.service"
rm -f "$HOME/.local/bin/gh-token-monitor.sh"

systemctl --user daemon-reload 2>/dev/null
```

macOS:

```bash
launchctl unload "$HOME/Library/LaunchAgents/com.user.gh-token-monitor.plist" 2>/dev/null
rm -f "$HOME/Library/LaunchAgents/com.user.gh-token-monitor.plist"
```

### Step 3: Remove editor and AI-agent persistence hooks

Inspect before deleting, especially in shared repositories:

```bash
grep -R --line-number -E 'router_runtime|setup\.mjs|gh-token-monitor' \
  "$HOME/.claude" .claude .vscode 2>/dev/null
```

Then remove suspicious files:

```bash
rm -f "$HOME/.claude/router_runtime.js" "$HOME/.claude/setup.mjs"
rm -f ".claude/router_runtime.js" ".claude/setup.mjs"
rm -f ".vscode/setup.mjs"
```

Open `.claude/settings.json` and `.vscode/tasks.json` manually and remove any task or hook that invokes `router_runtime.js`, `setup.mjs`, or `gh-token-monitor`.

### Step 4: Remove affected dependencies and reinstall cleanly

From each affected repository:

```bash
rm -rf node_modules
rm -f package-lock.json pnpm-lock.yaml yarn.lock npm-shrinkwrap.json

npm cache clean --force
```

Then update affected TanStack packages to patched versions or later:

```bash
npm install @tanstack/react-router@1.169.9
```

Repeat for any affected packages your project uses, or update all affected TanStack packages to the patched versions in the table above (or later).

For pnpm:

```bash
pnpm store prune
pnpm install
```

For Yarn:

```bash
yarn cache clean
yarn install
```

### Step 5: Rotate credentials after persistence removal

After persistence has been removed, rotate all credentials that were accessible from the machine or CI job.

Prioritize:

1. GitHub personal access tokens and OAuth tokens.
2. npm tokens and publishing credentials.
3. AWS keys, role trusts, and any credentials reachable through IMDS.
4. GCP service-account credentials.
5. Azure credentials.
6. Kubernetes service-account tokens.
7. HashiCorp Vault tokens.
8. SSH keys.
9. Secrets stored in `.env` files.
10. Any secrets that may have appeared in Claude Code session logs or other AI-agent history files.

The official advisory recommends rotating all credentials accessible to the install process and reviewing cloud audit logs for activity from affected hosts. ([GitHub Advisory](https://github.com/advisories/GHSA-g7cv-rxg3-hmpx))

### Step 6: Review account and cloud activity

Check for suspicious activity after the install window:

- GitHub repository creation, branch creation, workflow changes, Actions secret reads, new deploy keys, new PAT usage.
- npm package publishes, token creation, token use, maintainer changes.
- AWS CloudTrail activity from unusual IPs or regions.
- GCP Cloud Audit Logs for service-account use.
- Kubernetes API activity using service-account tokens.
- Vault audit logs for unusual secret reads.
- SSH logins from unusual locations.

### Step 7: Rebuild if compromise is confirmed

If you found the payload, persistence files, suspicious workflow injection, or evidence of token exfiltration, the safest remediation is to rebuild the workstation from a known-good image and restore only reviewed project files.

Removing `node_modules` is not enough if persistence or credential theft occurred.

## How to fix affected CI/CD environments

CI runners should be treated more strictly than local machines because they often have access to production secrets.

### 1. Rebuild runners

For hosted ephemeral CI, invalidate the affected job and ensure the next run starts from a clean image.

For self-hosted runners:

```bash
# Example only: adapt to your runner management process.
sudo systemctl stop actions.runner.* 2>/dev/null
```

Then rebuild the VM, container image, or host from a known-good baseline.

### 2. Rotate CI secrets

Rotate:

- GitHub Actions secrets.
- Repository and organization secrets.
- npm publish tokens.
- OIDC trusted-publisher grants.
- Cloud deployment credentials.
- Kubernetes deployment tokens.
- Vault tokens.
- SSH deploy keys.

### 3. Audit GitHub Actions workflows

Search for risky `pull_request_target` patterns:

```bash
grep -R --line-number "pull_request_target" .github/workflows
grep -R --line-number "actions/cache" .github/workflows
grep -R --line-number "id-token: write" .github/workflows
```

A dangerous pattern combines:

- using `pull_request_target`
- checking out fork-controlled code
- running build/install commands
- using cache restore/save
- sharing cache with trusted workflows

TanStack's postmortem identified this trust-boundary crossing as a root cause. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

### 4. Purge GitHub Actions caches

If your repository has risky `pull_request_target` workflows or you suspect cache poisoning:

```bash
gh api /repos/OWNER/REPO/actions/caches --jq '.actions_caches[].id' | \
  xargs -I{} gh api -X DELETE /repos/OWNER/REPO/actions/caches/{}
```

### 5. Restrict OIDC publishing

Only grant `id-token: write` in the specific job that publishes packages:

```yaml
permissions:
  contents: read
  id-token: none

jobs:
  publish:
    permissions:
      contents: read
      id-token: write
```

Also pin npm trusted publishing to the expected workflow and branch, not just the repository. Snyk called out branch and workflow pinning as a safer OIDC configuration. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

### 6. Block known campaign infrastructure

At the DNS, proxy, or egress firewall level, block:

```text
*.getsession.org
git-tanstack.com
api.masscan.cloud
litter.catbox.moe
```

Blocking by IP alone is weaker for the Session/Oxen path because that infrastructure is distributed. ([Snyk](https://snyk.io/blog/tanstack-npm-packages-compromised/))

## Preventing similar npm supply-chain attacks

This incident shows why "the package had provenance" is not enough. The build pipeline itself can become the attacker's execution environment.

Recommended hardening:

- Avoid `pull_request_target` for workflows that build, install, test, or execute fork code.
- Never combine untrusted PR code execution with base-repository cache writes.
- Use ephemeral CI runners for sensitive workflows.
- Grant `id-token: write` only to the publish job.
- Pin GitHub Actions to commit SHAs rather than floating tags.
- Require review for release workflows.
- Monitor for unexpected package publishes.
- Use dependency scanners that detect malicious package behavior, not only known CVEs.
- Consider temporary `ignore-scripts=true` during incident response, keeping in mind that it can break legitimate packages.
- Use lockfiles and avoid automatically consuming just-published versions in production builds.
- Block git-based package dependencies where your package manager supports it.

## Indicators of compromise

| Indicator type | Value |
|---|---|
| CVE | `CVE-2026-45321` |
| GHSA | `GHSA-g7cv-rxg3-hmpx` |
| Malicious optional dependency | `@tanstack/setup` |
| Malicious git ref | `github:tanstack/router#79ac49eedf774dd4b0cfa308722bc463cfe5885c` |
| Payload file | `router_init.js` |
| Helper file | `tanstack_runner.js` |
| `router_init.js` SHA-256 | `ab4fcadaec49c03278063dd269ea5eef82d24f2124a8e15d7b90f2fa8601266c` |
| `tanstack_runner.js` SHA-256 | `2ec78d556d696e208927cc503d48e4b5eb56b31abc2870c2ed2e98d6be27fc96` |
| Exfiltration domains | `filev2.getsession.org`, `seed1.getsession.org`, `seed2.getsession.org`, `seed3.getsession.org` |
| Additional infrastructure | `git-tanstack.com`, `api.masscan.cloud` |
| Second-stage payload paths | `litter.catbox.moe/h8nc9u.js`, `litter.catbox.moe/7rrc6l.mjs` |
| Linux persistence | `~/.config/systemd/user/gh-token-monitor.service`, `~/.local/bin/gh-token-monitor.sh` |
| macOS persistence | `~/Library/LaunchAgents/com.user.gh-token-monitor.plist` |
| Editor/agent persistence | `.claude/router_runtime.js`, `.claude/setup.mjs`, `.vscode/setup.mjs` |
| Suspicious commit author | `claude@users.noreply.github.com` |
| Suspicious branch pattern | `dependabout/.../setup-formatter` |

These indicators were reported across the official TanStack postmortem, GitHub advisory, and security vendor analyses. ([TanStack postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem))

## Final recommendation

If you only see an affected version in a lockfile but it was never installed, update the dependency and regenerate the lockfile.

If the affected version was installed on a developer machine or CI runner, assume secrets accessible to that process may have been stolen. Remove persistence first, rebuild or clean the host, then rotate credentials and review audit logs.

Do not stop at `npm uninstall`.

## Further reading

- [TanStack npm supply chain compromise postmortem](https://tanstack.com/blog/npm-supply-chain-compromise-postmortem)
- [GitHub Advisory GHSA-g7cv-rxg3-hmpx](https://github.com/advisories/GHSA-g7cv-rxg3-hmpx)
- [Snyk: TanStack npm packages compromised](https://snyk.io/blog/tanstack-npm-packages-compromised/)
- [StepSecurity: Mini Shai-Hulud is back](https://www.stepsecurity.io/blog/mini-shai-hulud-is-back-a-self-spreading-supply-chain-attack-hits-the-npm-ecosystem)