TanStack の npm サプライチェーン攻撃(2026年5月)でやるべき確認と対応

  • サプライチェーン
  • npm
  • TanStack
  • GitHub Actions
Severity
Critical
Status
対応済み
CVE
CVE-2026-45321
Affected
@tanstack/router, @tanstack/react-router, @tanstack/react-start

また npm のサプライチェーン攻撃です。今度は TanStack。

2026年5月12日早朝、TanStack の npm パッケージ群にマルウェア入りのバージョンが公開されました。

先日の axios の件と似ている部分もあります。

ただし、今回の中身はかなり違います。

メンテナーの npm トークンを盗んで公開したのではなく、GitHub Actions の pull_request_target、Actions キャッシュ、OIDC Trusted Publishing がつながって、正規の公開経路から不正パッケージが出てしまった、という話です。

ただし、マルウェア自体はインストール先の npm トークンも狙います。

「TanStack 側の公開に npm トークンは使われていない」と「入れてしまった環境の npm トークンは盗まれうる」は、分けて確認したほうがいいです。

しかも、@tanstack/query-core@tanstack/react-query ではありません。

ここ、名前だけで早とちりしやすいので先に書いておきます。

何が起きたか

TanStack 公式のポストモーテムによると、UTC 2026-05-11 19:20〜19:26 の約6分間に、42個の @tanstack/* パッケージへ合計84個の不正バージョンが公開されました。

日本時間では 2026年5月12日 04:20〜04:26 頃です。

(寝てる人が多い時間で、まずよかったです)

GitHub Security Advisory では CVE-2026-45321、Critical、CVSS 9.6 として扱われています。

不正バージョンは公開から約20分で外部研究者に検知され、TanStack 側も対応を開始しています。ただし、npm の仕様上すぐに全パッケージを unpublish できるとは限らないため、「公開窓が6分だったから安全」とは言い切れません。

今回の攻撃経路はざっくりこうです。

  1. 攻撃者が TanStack/router の fork から PR を作る
  2. pull_request_target で動く GitHub Actions が、fork 側のコードを実行してしまう
  3. その実行で GitHub Actions のキャッシュが汚染される
  4. 後続の正規リリースワークフローが汚染済みキャッシュを復元する
  5. ランナー上で攻撃コードが動き、OIDC トークンをメモリから抜く
  6. その OIDC トークンを使って npm に不正バージョンを公開する

ポイントは、TanStack 側の公開経路としては、npm トークンが盗まれたわけではないこと。

npm から見ると、正規の GitHub Actions OIDC Trusted Publisher 経由で公開されているように見えます。ここがかなり嫌なところです。

影響を受けたパッケージ

影響を受けたのは Router / Start 系が中心です。よく見かけるものだけ抜き出すと、以下のようなパッケージが含まれます。

パッケージ不正バージョン
@tanstack/react-router1.169.5, 1.169.8
@tanstack/router-core1.169.5, 1.169.8
@tanstack/history1.161.9, 1.161.12
@tanstack/router-cli1.166.46, 1.166.49
@tanstack/router-plugin1.167.38, 1.167.41
@tanstack/router-vite-plugin1.166.53, 1.166.56
@tanstack/react-start1.167.68, 1.167.71
@tanstack/start-client-core1.168.5, 1.168.8

全42パッケージの一覧は GitHub Security Advisory に載っています。

逆に、TanStack 公式は以下のファミリーを「確認済みでクリーン」としています。

  • @tanstack/query*
  • @tanstack/table*
  • @tanstack/form*
  • @tanstack/virtual*
  • @tanstack/store
  • @tanstack/start(メタパッケージ。@tanstack/start-* ではない)

なので、@tanstack/react-query だけを使っているプロジェクトを見て「TanStack だから全部アウト」と判断する必要はありません。

もちろん、同じリポジトリ内で @tanstack/react-router なども使っているなら別です。

マルウェアは何をするか

不正パッケージの package.json には、次のような optionalDependencies が入っていました。

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

@tanstack/setup は npm に存在する正規パッケージではありません。

Sponsored

GitHub の tanstack/router fork network 上にある孤立コミットを参照し、そこで prepare が走ります。最終的に、パッケージ内に紛れ込んだ約2.3MBの難読化 JavaScript router_init.js が実行される仕組みです。

狙われる情報はかなり実務寄りです。

  • AWS の IMDS / Secrets Manager
  • GCP の metadata service
  • Kubernetes の service account token
  • HashiCorp Vault token
  • ~/.npmrc の npm トークン
  • GitHub token(環境変数、gh CLI、.git-credentials
  • SSH 秘密鍵

さらに、被害環境が npm パッケージのメンテナーであれば、そのメンテナーが持つ別パッケージを列挙し、同じ注入を広げようとします。

つまり単なる情報窃取ではなく、自己増殖する npm ワーム寄りの挙動です。

実行後にバックグラウンドで動き続ける挙動も報告されています。

なので、node_modules を消して入れ直しただけで終わり、とは考えないほうがいいです。

影響を受けているか確認する

まずは lockfile を見ます。

# package-lock.json / pnpm-lock.yaml / yarn.lock をまとめて見る
grep -R '79ac49eedf774dd4b0cfa308722bc463cfe5885c' \
  package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

grep -R '@tanstack/setup' \
  package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

ヒットしたら、かなり強い疑いがあります。

次に、代表的な影響パッケージを見ます。

grep -E '@tanstack/(react-router|router-core|history|router-cli|router-plugin|router-vite-plugin|react-start|start-client-core)' \
  package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null

この結果に、GitHub Security Advisory に載っている不正バージョンが含まれていないか確認します。

ただし、これはよく使われそうなものを抜いた確認です。

実際には 42 パッケージが対象なので、@tanstack/solid-*@tanstack/vue-*@tanstack/start-* などを使っている場合は Advisory の全リストを見てください。

インストール済みの node_modules が残っている場合は、router_init.js も見ます。

find -L node_modules/@tanstack -name router_init.js -print 2>/dev/null

ヒットしたらアウト寄りです。

また、不正パッケージを安全に確認したい場合は、npm install ではなく npm pack を使います。

npm pack @tanstack/react-router@1.169.5
tar -xzf tanstack-react-router-1.169.5.tgz
grep -A3 optionalDependencies package/package.json
ls -la package/router_init.js

npm pack は tarball を落とすだけなので、install lifecycle script は実行されません。

ただし、npm 側で tarball が削除済みなら取得できません。その場合は手元の lockfile、CI ログ、キャッシュ、node_modules を優先して見ます。

4時台という話

今回の公開窓は日本時間で 2026年5月12日 04:20〜04:26 頃です。

人間が手で npm install を叩く時間としては、かなり少なそうです。

ただ、これは人間に限った話です。

GitHub Actions は寝ません。

たとえばこういう環境は確認したほうがいいです。

  • schedule: トリガーで早朝に CI を回している
  • Renovate / Dependabot の PR を自動マージしている
  • CI で lockfile が更新されうる状態の npm install を使っている
  • lockfile をコミットしていない
  • @tanstack/react-router@tanstack/react-start を使っている

特に CI ランナーは、GitHub token、クラウド認証情報、デプロイ用の秘密情報を持っていることが多いです。

手元の開発PCより CI のほうが危ない、というケースは普通にあります。

GitHub Actions / 自動化まわりの確認

まず見るべき時間帯は、UTC 2026-05-11 19:20〜19:30 です。

日本時間なら 2026年5月12日 04:20〜04:30。

念のため、前後を広めに見てもいいです。

gh run list --repo <owner>/<repo> --limit 100

確認するポイントはこのあたりです。

  • 該当時間帯に npm install / npm ci / pnpm install / yarn install が走っていたか
  • そのプロジェクトが影響パッケージを使っていたか
  • lockfile に不正バージョンが入っていたか
  • Renovate / Dependabot が TanStack 関連の PR を作成またはマージしていないか
  • CI ランナーに渡していた secret の範囲はどこまでか

npm ci を使っていて、かつ攻撃前の lockfile がコミットされていたなら、基本的には不正バージョンを引きません。

ただし、lockfile 自体に不正バージョンが入っていた場合は別です。npm ci は lockfile を忠実に再現するので、不正バージョンも忠実に入れます。

ここは雑に「npm ci なら安全」と言い切らないほうがいいです。

lockfile と npm ci の話

axios のときにも書きましたが、今回も lockfile の有無がかなり効きます。

# 攻撃を受けやすい運用
npm install

# まだ堅い運用
npm ci

ここで言っている npm install は、lockfile がない、古い、CI で更新される、といった運用の話です。

Sponsored

package-lock.json が安全なバージョンを指していて、package.json と整合しているなら、npm install だから即アウトというわけではありません。

npm ci は lockfile に書かれたバージョンを再現します。

攻撃前の lockfile に安全なバージョンが固定されていれば、レジストリに不正バージョンが出ても勝手には上がりません。

逆に、CI で lockfile が更新されうる状態の npm install を使っている、lockfile をコミットしていない、Renovate の自動マージで新バージョンを即取り込む、という運用は刺さりやすいです。

今回のように lifecycle script が起点になる攻撃には、--ignore-scripts も効きます。

npm ci --ignore-scripts

ただし、ネイティブモジュールなど正当な postinstall / prepare が必要なパッケージもあります。いきなり全環境で入れるとビルドが壊れることもあるので、CI で試してからがいいです。

影響を受けていた場合の対応

不正バージョンをインストールしていた場合、パッケージを入れ替えるだけでは足りません。

install 時点でコードが走っているので、その環境は侵害済みとして扱います。

優先度順にやるならこうです。

  1. 該当環境をネットワークから切り離す
  2. lockfile、node_modules、CI ログ、ランナーの作業ディレクトリを保全する
  3. 長時間プロセスや不審な Node.js プロセスを止める、または環境ごと捨てる
  4. 別の安全な環境で、対象パッケージを修正版へ上げる、または攻撃前の既知の安全バージョンへ戻す
  5. その環境からアクセスできた secret をローテーションする
  6. クラウド監査ログ、GitHub audit log、npm publish history を確認する
  7. 可能ならマシンや CI ランナーを作り直す

同じ開発機を使い続ける場合は、永続化の痕跡も見たほうがいいです。

Snyk の解析では、.claude/router_runtime.js.claude/setup.mjs.vscode/setup.mjs~/.local/bin/gh-token-monitor.sh~/.config/systemd/user/gh-token-monitor.service~/Library/LaunchAgents/com.user.gh-token-monitor.plist などに残る挙動も報告されています。

ここが残ったまま secret をローテーションすると、新しい secret もまた抜かれます。

ローテーション対象は、少なくとも以下です。

  • npm トークン
  • GitHub token / PAT
  • SSH 秘密鍵
  • AWS / GCP / Azure の認証情報
  • Kubernetes service account token
  • Vault token
  • GitHub Actions Secrets(該当ジョブに渡していたもの)
  • .env に入れていた API key

特に GitHub Actions の self-hosted runner で動いていた場合は、雑に片付けないほうがいいです。

ランナーが触れた secret の範囲がそのまま被害範囲になります。

まとめ

今回は「TanStack Query がやられた」という話ではありません。

公式に確認済みでクリーンとされている @tanstack/query* と、実際に影響を受けた Router / Start 系を分けて見る必要があります。

一方で、攻撃の中身はかなり重いです。npm トークンを盗んで公開したのではなく、GitHub Actions の正規リリース経路を踏み台にして、OIDC Trusted Publishing で不正パッケージを出しています。

信頼された経路から出ているから安全、とは言えない。

今回の確認ポイントはシンプルです。

  • @tanstack/react-router など影響パッケージを使っているか
  • 2026年5月12日 04:20〜04:30 頃に install が走っていないか
  • lockfile に不正バージョンや @tanstack/setup が入っていないか
  • CI ランナーの secret をどこまでローテーションすべきか

そして今後の対策も、結局は地味なところです。

lockfile をコミットする。CI では npm ci を使う。自動マージは少し待たせる。必要なら --ignore-scripts を検討する。

派手ではないけど、こういう運用がだいぶ効きます。

参考リンク