npmワーム「Shai-Hulud」再来——keyv侵害868パッケージと.claude/settings.jsonという新しい侵入口

2026年8月4日、npmのkeyv・cacheable系パッケージが自己増殖ワーム「Shai-Hulud」に乗っ取られ、868パッケージ・1,381バージョンが汚染。有効なprovenance attestationが付いたまま配布され、.claude/settings.jsonのSessionStartフックで永続化する新しい手口の全貌と、いま確認すべきことを整理します。

npmワーム「Shai-Hulud」再来——keyv侵害868パッケージと.claude/settings.jsonという新しい侵入口

2026年8月4日、npmエコシステムで最も地味で、最も広く使われている部類のライブラリが乗っ取られた。キーバリューストアの薄い抽象化レイヤーである keyv、そしてESLintがリント済みファイルを記憶するために使う flat-cachefile-entry-cache。いずれも、ほとんどの開発者が package.json に自分で書いた覚えのないパッケージだ。

Aikido Securityの集計では、同日午後の時点で868パッケージ・1,381バージョンが汚染され、その合計は月間20億インストールを超える。ゼロデイでもタイポスクワッティングでもない。メンテナのGitHubアカウントが1つ乗っ取られただけで、JavaScript世界の依存ツリーの深部に一斉到達した。

今回のキャンペーンには、これまでになかった要素が2つある。悪性パッケージに有効なprovenance attestationが付いていたこと。そして .claude/settings.json というAIコーディングエージェントの設定ファイルが、永続化の足場として使われたことだ。

1アカウントの侵害が、なぜ868パッケージに広がったのか

攻撃者が押さえたのは、npmユーザー jaredwray のGitHubアカウントだった。このメンテナは keyv のほか cacheablecacheable-requestflat-cachefile-entry-cachecache-manager を2つのネームスペースにまたがって単独で管理していた。依存ツリーの深部にあり、更新頻度が低くて誰も差分を読まず、しかも1人が広範囲を管理している——1アカウント、巨大な爆発半径。

Socketがnpmレジストリの公開時刻から再構成したタイムラインでは、8月4日14時05分(UTC)に [email protected] が公開され、15時39分から15時44分までのわずか5分間に cacheable 系9パッケージが一斉に押し出された。並行して jaredwray/keyv リポジトリでは main へのforce-pushと v6.0.0 タグの削除・再作成が繰り返されている。攻撃者は撃ち込んで立ち去ったのではなく、アカウントとCIの制御を握ったまま履歴を書き換え続けていた。

影響を受けた主なパッケージは以下のとおり(安全なバージョンはGMO Flatt Securityの対応指針による)。

パッケージ 悪性バージョン 安全なバージョン
keyv 6.0.0 5.6.0 以下
file-entry-cache 11.1.6 11.1.5 以下
cacheable-request 13.0.20 13.0.19 以下
@cacheable/utils 2.5.1 2.5.0 以下
@cacheable/memory 2.2.1 2.2.0 以下
cache-manager 7.2.10 7.2.9 以下

このほか [email protected] も汚染が報告されている。第二波ではワームが他メンテナのアカウントへ伝播し、Deliveroo、Picsart、Qlik、Ornikarの企業ネームスペースにも到達した。

インストールした瞬間に成立する

侵害されたパッケージは、package.jsonfilessetup.mjsMath_Symbol.js を追加し、"preinstall": "node setup.mjs" というフックを1行仕込んでいた。一方で dist/ の出力は直前のクリーンリリースとバイト単位で同一のまま。ライブラリとしての機能は正常に動くので、動作確認では気づけない。そして preinstall はパッケージが展開される前に走る。importも、アプリケーションコードからの呼び出しも、到達可能性の分析も要らない。依存関係をインストールすること自体が、そのままエクスプロイトになる。

setup.mjs の仕事は第2段のインタプリタを取ってくることだけだ。ホストに bun がなければ、GitHub Releasesから公式のBun 1.3.13バイナリをダウンロードする。この「自前のランタイムを持ち込む」設計には、はっきりした狙いがある。ホストがどのNodeバージョンに固定されていようと関係なくなり、NODE_OPTIONS などNodeレベルの計装や node のプロセスツリーを前提に書かれたEDRルールをすり抜けられる。しかもダウンロード元は github.com で、これを遮断できるegressポリシーは事実上存在しない。取ってくるのは正規のソフトウェアであり、悪意があるのは「それに何を実行させるか」だけだ。

実行される Math_Symbol.js は約728KBのBunバンドルで、多形basE91エンコードによって文字列が保護されている。収集対象は網羅的だ。AWS・GCP・Azureの認証情報とEC2インスタンスメタデータ、npmトークンとGitHubの各種トークン、Vault(6経路から取得を試み全マウントのKVを読み出す)、Kubernetesのサービスアカウントトークン、SSH鍵、Terraformのstate、Stripeキー、Slackトークン、暗号通貨ウォレット、そしてAIツールの設定ファイル。約200のglobパターンによるファイルスイープが、TruffleHog方式の正規表現エンジンと並走する。

GitHub Actionsランナー上では、さらに踏み込む。ランナープロセスのメモリを直接読み、インメモリのシークレットストアを丸ごとダンプするのだ。ログでマスクされているシークレットは、そのジョブ自身からは守られていない。

provenanceは「誰が書いたか」を保証しない

今回の最も居心地の悪い事実は、悪性の [email protected] に有効なnpm OIDC provenance attestationが付いていたことだろう。

攻撃者は公開済みのtarballを改ざんしていない。ソースリポジトリにコミットし、あとはメンテナ自身のリリース自動化にパッケージングと署名をさせた。ビルドパイプラインは正直に「この成果物は、このリポジトリのこのコミットからビルドされました」と証明している。そのコミットが既に汚染されていただけだ。

サプライチェーンのポリシーが「provenanceを要求し、それが通れば信頼する」であれば、このキャンペーンは正面から素通りする。attestationは「ビルドがどこから来たか」の主張であって、「そのソースが正直だったか」の主張ではない。トークンを直接使った不正な公開に対しては依然として有効な防御層だが、CI/CDパイプラインそのものが侵害されたケースには効かない。アカウントのセキュリティこそがサプライチェーンのセキュリティであり、attestationはその代替にはならない。

ワーム化と、AIエージェント設定を狙う永続化

認証情報の窃取だけなら、まだ「大きな事故」の範囲に収まる。このキャンペーンを高くつくものにしているのは [publish] モジュールだ。

盗んだnpmトークン、あるいはOIDCで発行した資格情報を使って、ペイロードは被害者のIDを確認し、そのメンテナが公開できるパッケージを列挙する。trusted publishingが設定されていればOIDCの交換エンドポイントから公開資格をその場で発行し、対象のtarballに同じ2ファイルと preinstall フックを注入して、integrityとshasumを再計算しマニフェストの整合性を保ったまま、バージョンを上げてレジストリへ書き戻す。

感染パッケージをインストールした開発者が、次の公開者になる。10パッケージが数百に膨れ上がるのに1営業日もかからなかったのは、これが理由だ。しかも正規のtrusted publishing経由で再公開されるため、ワームの出力もまた有効なattestationを継承しうる。

掃除が終わったと思ったチームを後から刺すのが、リポジトリ側に仕込まれた自動起動フックである。.vscode/tasks.json には folderOpen トリガのタスクが、.claude/settings.json には SessionStart フックが追加されていた。どちらも同じローダーを実行し、npm install を一切必要としない。インシデントを調べるためにリポジトリをcloneし、エディタで開くかコーディングエージェントを向けるだけで、調査に使っている端末が再感染する。「影響リポジトリをcloneして差分を見る」と書かれた対応手順書は、今日からサンドボックスを前提に書き直す必要がある。Jsmonはこれを、AIコーディングエージェントの設定ファイルを第一級の永続化機構として扱った、広く観測された初のキャンペーンだと評価している。

持ち出し経路も一点集中を避けている。被害者自身のGitHubアカウントに作ったリポジトリ、DNS経由の持ち出し、npm-cache[.]com というプロキシログで目立たない名前のHTTPSエンドポイント。加えて公開Ethereum RPCノードをC2に使っており、攻撃者が保有するドメインが存在しないためテイクダウンでチャネルを潰せない。ビルドエージェントからのクラウドインスタンスメタデータ読み取りや、/tmp/bun-dl-* のような一時ディレクトリの残骸も、痕跡として残る。

影響と今後:いま確認すべきこと

2025年9月の初代Shai-Hulud(190パッケージ)、11月のShai-Hulud 2.0(796パッケージ)、2026年3月のaxios侵害、4〜5月のMini Shai-Hulud、7月のAsyncAPI侵害、そして今回。npmエコシステムへのサプライチェーン攻撃は、もはや単発の事故ではなく継続的な圧力になっている。

即座にやることは3つだ。

  1. lockfileを推移的依存も含めて確認し、クリーンなバージョンにピン留めしてintegrity hashでロックする。キャレットやチルダのレンジは残さない。
  2. ローテーションではなく失効させる。ローテーションでは古い認証情報が期限まで有効なまま残る。npmトークンとGitHubトークンはrevokeし、クラウド・Vault・Kubernetes・SSH・Stripe・Slackも爆発半径に含まれるなら再発行する。
  3. 影響バージョンをインストールしてスクリプトを実行した端末とCIランナーは、清掃ではなく再構築する。あわせて、clone済みの全リポジトリの .vscode/tasks.json.claude/settings.json を確認する——調査中にcloneしたものも含めて。

恒久策は2つ。CI/CDで npm ci --ignore-scripts を標準ポリシーにすること(npm v12ではlifecycle scriptがデフォルトで無視されるが、CIのnpmバージョンが混在しているなら明示指定を続けたほうがいい)。そして .npmrcmin-release-age による検疫期間の設定だ。npm v12はデフォルトで1日のcooldownが有効だが、今回の悪性版がテイクダウンまで数時間かかったことを考えると7日への引き上げが現実的だろう。実際、検疫期間を設けていた環境はそもそもインストールに至っていない。

この事件の教訓は「あなたの本当の依存グラフは、認証情報の爆発半径である」という一点に尽きる。問うべきは「どのパッケージを入れたか」ではなく「その端末から見えていたもので、攻撃者はどこまで到達できるか」だ。

よくある質問

keyvを直接インストールしていなければ安全か

安全とは言えない。今回影響を受けたパッケージの多くは、ESLintやHTTPクライアント、ビルドツールの推移的依存として入り込んでいる。被害を受けたチームのほとんどは、これらを意図してインストールしていない。package.json ではなくlockfileを、推移的依存まで含めて確認する必要がある。

provenance attestationを検証していれば防げたか

防げない。悪性の [email protected] はメンテナ自身のGitHub Actionsパイプラインから公開されたため、有効なprovenance attestationが付いていた。provenanceは「どのリポジトリのどのコミットからビルドされたか」を保証するだけで、そのコミットが正当な作者によるものかは保証しない。トークンを直接使った公開に対しては依然として有効な防御層だが、パイプライン自体が侵害されたケースには効かない。

感染したかどうかはどう確認すればよいか

lockfileに悪性バージョンが含まれていないかを確認したうえで、node_modules 以下に setup.mjsMath_Symbol.js がないか、/tmp/bun-dl-* のような一時ディレクトリが残っていないか、身に覚えのない bun バイナリがないかを探す。ネットワーク側では npm-cache[.]com への通信、CIランナーからのクラウドインスタンスメタデータ読み取り、ビルド基盤からの異常なDNSクエリ量が手がかりになる。

AIコーディングエージェントを使っていると危険が増すのか

今回のキャンペーンに限れば、.claude/settings.jsonSessionStart フックが永続化に使われたため、汚染されたリポジトリを開いてエージェントを起動するだけでローダーが再実行される。npm install は不要だ。エージェントを使うこと自体が危険なのではなく、設定ファイルがリポジトリに同梱され開いた瞬間に実行される仕組みがリスクになる。信頼できないリポジトリはサンドボックスで開き、設定ファイルの差分を確認する運用が要る。

まとめ

1つのGitHubアカウントの乗っ取りが、1営業日で868パッケージ・月間20億インストール規模の汚染に化けた。有効なprovenanceが付いたまま配布され、preinstall フックによってインストールした瞬間に成立し、盗んだ資格情報で自分自身を他のパッケージへ再公開する。

最も新しい要素は、.vscode/tasks.json.claude/settings.json が永続化に使われたことだ。npm installを介さない感染経路が生まれた以上、「リポジトリを開く」という日常動作そのものが信頼境界の外側に出たことになる。install時のスクリプト実行は、このエコシステムで最も弱い環に居座り続けている。その瞬間に効く制御は「スクリプトを実行しない」と「そのパッケージを入れない」の2つしかない。

参考ソース

関連記事