2026年5月11日、JavaScriptエコシステムを揺るがす大規模なサプライチェーン攻撃が発生しました。TanStack(旧React Query)の42パッケージ・84バージョンが汚染され、インストールしただけで認証情報が盗み出されるマルウェアが仕込まれていたんですよね。
私は朝のニュースチェックでこの件を知ったのですが、攻撃手法の巧妙さに正直ぞっとしました。今回はこのインシデントの全貌と、そこから得られる教訓を整理してみたいと思います。
何が起きたのか
攻撃者はまずTanStack/routerリポジトリをフォークし、わざと「configuration」という無害な名前にリネームしました。そこに約3万行の難読化されたJavaScriptペイロードを仕込んだコミットを作成。コミッターの名前を「claude」と偽装し、コミットメッセージに[skip ci]をつけてCI実行を回避するなど、細部まで計算された手口です。
次にPull Requestを送り、pull_request_targetトリガーを利用してGitHub Actionsのキャッシュを汚染しました。pull_request_targetはフォークからのPRでもベースリポジトリの権限で実行されるため、攻撃者のコードがメインリポジトリのキャッシュに書き込めてしまったのです。
そして正規のメンテナーが別のPRをマージした瞬間、リリースワークフローが汚染済みキャッシュを読み込み、マルウェアがOIDCトークンを生成してnpmに直接パブリッシュ。わずか6分間で84の汚染バージョンがリリースされました。
攻撃の3つの要点
### pull_request_targetの信頼境界問題
pull_request_targetは「フォークからのPRでもベースリポジトリのシークレットにアクセスできる」という強力なトリガーです。これ自体はGitHub Actionsの仕様なのですが、フォークのコードをチェックアウトして実行すると、事実上フォーク作者にベースリポジトリの権限を渡してしまいます。以前から「Pwn Request」パターンとして知られていた脆弱性ですが、今回ついに大規模な被害につながりました。
### キャッシュ汚染という見えない攻撃面
GitHub Actionsのキャッシュはワークフロー間で共有されます。攻撃者はPRトリガーのワークフローでキャッシュを書き込み、リリースワークフローがそのキャッシュを読み込むという構造を悪用しました。キャッシュは通常「高速化のための仕組み」として信頼されがちですが、実は重要なセキュリティ境界でもあるんですよね。
### OIDCトークンの横取り
npm trusted publisherの仕組みでは、ワークフロー自体がOIDCトークンを発行してパッケージを公開します。攻撃者のマルウェアはワークフロー実行中にこのトークン発行権限を横取りし、正規のパブリッシュステップをバイパスして直接npmにパッケージを送りつけました。npmトークンそのものは盗まれていない点が、従来の攻撃パターンとは異なります。
考察・まとめ
今回の攻撃で印象的なのは、外部研究者が20分で検出したスピードです。StepSecurityのashishkurmi氏が汚染を発見し、即座にIssueを立ててnpmセキュリティに通知しました。コミュニティの監視体制がなければ、被害はさらに拡大していたと思います。
開発者として私たちが今すぐできることは3つあります。まず、pull_request_targetを使うワークフローの見直し。次に、GitHub Actionsのキャッシュキーにワークフロー名やトリガーの種類を含めて隔離すること。そして、依存パッケージのバージョンロックとインストール時の差分チェックの習慣化です。
ソフトウェアサプライチェーンの安全性は、もはや個々のプロジェクトだけの問題ではありません。CI/CDパイプライン、パッケージレジストリ、認証の信頼チェーンすべてが攻撃面になり得ることを、このインシデントは改めて示してくれました。
← ブログ一覧に戻る