トークンの失効が引き金だった
10 月 8 日、Shai-Hulud ワームを仕込んだ tensorlake 0.5.144 が npm に公開されました。GitHub、npm、クラウド、SSH の認証情報を盗み、被害者のリポジトリに .claude/settings.json を仕込み、盗んだ GitHub トークンを 60 秒ごとに確認して、それが使えなくなった瞬間に rm -rf ~/ を実行するモニターをインストールします。インシデント対応の定石の第一歩であるトークンの失効が、被害者のホームディレクトリを消し去るのです。Bromure Agentic Coding では、デフォルトの経過日数ゲートがこのバージョンを npm から隠し、マシン内の GitHub トークンは最初から本物ではありませんでした。
ワームは、見つけた人に向けた文字列をコードに残していました。 「IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner」。このトークンを失効させれば、 持ち主のコンピューターを消去する、という意味です。コードは書いてあるとおりに動きます。
10 月 8 日(水)01:12 UTC、tensorlakeai/tensorlake のリリースワークフローが
tensorlake SDK のバージョン 0.5.144 を npm に公開しました。Tensorlake は AI
エージェント向けのサーバーレス・サンドボックス基盤で、その npm パッケージは週におよそ
12,000 回ダウンロードされています。新しいバージョンはプロジェクト自身のリポジトリから、
プロジェクト自身のワークフローでビルドされたもので、npm には来歴(provenance)の証明も
表示されていました。
Socket は 11 分後にこれを検知しました。 StepSecurity、 Aikido、 The Hacker News も同じ日に続きました。このバージョンには Shai-Hulud ワームの新しい亜種が入っていて、 今回は人質を取ります。
どうやって入り込んだか
StepSecurity がタイムラインを再構成しています。10 月 7 日 01:20 UTC、何者かがメンテナー の名前でリポジトリのメインブランチに直接コミットを push しました。その後数時間でさらに 7 件の コミットが続きました。誰も気づかず、翌日リリースワークフローが main のコードをビルドして 公開しました。来歴の証明は正確でした。パッケージがどこでビルドされたかを証明していて、その 場所はすでに攻撃者の手に落ちていたのです。
パッケージの package.json には 1 行が加わっていました。
"preinstall": "node lib/setup.mjs"
npm は、パッケージを所定の場所に展開するより前に preinstall を実行します。このスクリプト
は Bun ランタイムをダウンロードし、それを使って難読化された 856 KB のペイロード
lib/Math_Symbol.js を実行します。CI 上では何もしません。StepSecurity のまとめは
「狙いは開発者のマシン」です。
何を盗み、どこへ送るか
ペイロードは、GitHub と npm のトークン、AWS・Azure・GCP のクラウドキー、Kubernetes と
HashiCorp Vault のシークレット、SSH キー、.env ファイル、ブラウザーに保存されたログイン
情報、暗号資産ウォレット、そして AI コーディングツールの設定(.claude、.cursor、
.kiro、Windsurf、Zed)を集めます。集めたものを暗号化して iseekaigogo.com に送り、
失敗すれば被害者自身の GitHub アカウントに公開リポジトリを作ってそこに置きます。説明文は
「Shai-Hulud: Here We Go Again.」です。ドメインが落ちると、約 30 の公開 RPC エンドポイント
を通じて Ethereum のコントラクトから新しいドメインを探します。
そして広がります。被害者の npm トークンで、その人のパッケージを一つずつダウンロードし、
自分を追加し、バージョンを上げて再公開します。GitHub トークンでは、
.claude/settings.json と .vscode/tasks.json を被害者のリポジトリにコミットします。
コミット者は [email protected]、メッセージは
chore: update dependencies です。そのプロジェクトを次に Claude Code や VS Code で開いた
開発者が、ワームをもう一度実行することになります。StepSecurity の Ashish Kurmi が言うとおり、
このマルウェアは「誰かが Claude Code や VS Code でプロジェクトを開くと再び動く」のです。
人質
新しいのは gh-token-monitor というサービスです。Linux では
~/.config/systemd/user/ の systemd ユーザーユニット、macOS では
~/Library/LaunchAgents/com.user.gh-token-monitor.plist の LaunchAgent、Windows では
monitor.ps1 を実行するログオンタスクです。StepSecurity はその動作をこう説明しています。
60 秒ごとに、最大 24 時間、サービスはそのトークンを GitHub API で確認する。GitHub が それを拒否すると、サービスは
rm -rf ~/を実行する。
あなたが被害者だとしましょう。インストールしたパッケージが GitHub トークンを盗んだと知り
ます。定石では、すぐに失効させることになっています。失効させると、1 分以内にホームディレクトリ
が消えます。チェックアウト、コミットしていない作業、dotfiles、そしてワームが何をしたかの
証拠まで。Socket は、その脅しがコードの中にリテラル文字列として書かれているのを見つけました。
IfYouRevokeThisTokenItWillWipeTheComputerOfTheOwner。
そのため、各社のアドバイザリーには手順の順番に関するルールが付くようになりました。 StepSecurity のステップ 3 は、モニターを見つけて削除すること、そしてその前に失いたくない ものをすべてバックアップすることです。太字の警告はこうです。「ステップ 3 が終わるまで、 GitHub トークンを失効させないでください」。Socket も同じことを言っています。モニター探しに かかる 1 時間は、攻撃者があなたの push 権限付きの有効なトークンを握っている 1 時間です。
Bromure のマシンで同じインストールをすると
Bromure Agentic Coding では、Claude Code や Codex などのエージェントが Mac 上の Linux VM
で動きます。マシン 1 台につき VM 1 台です。VM が開く接続は、npm の取得も含めてすべて Mac
上のプロキシを通ります。そうしたマシンの 1 台で 10 月 8 日を再現してみましょう。エージェント
がプロジェクトに tensorlake を追加し、npm install を実行します。
そのバージョンはまだ存在しなかった
新しいマシンでは、サプライチェーンの経過日数ゲートが最低 2 日の設定で有効になっています。
npm がレジストリに tensorlake のバージョン一覧を求めると、プロキシは公開から 2 日未満の
バージョンをすべて取り除き、latest を十分に古い最新バージョンに向け直します。10 月 8 日には
それが 0.5.143 でした。npm はそれをインストールして先に進みました。マシンの中からは 0.5.144
は一度も存在せず、それが基準を超えるころには npm から消えていました。
ロックファイルやエージェントが 0.5.144 を名指しで求めた場合、プロキシは HTTP 451 で ダウンロードを拒否し、npm がそのまま表示する理由を返します。
Bromure Supply-Chain Security blocked this request: npm package
[email protected] published 3 hours ago — policy requires 2 days minimum
エージェントもこの行を読み、自分で一つ前のバージョンに固定します。
同じ Supply Chain パネルには、さらに 2 つの層があります。socket.dev を選んで
Block compromised packages をオンにしていれば、socket.dev がパッケージにフラグを立てた
時点でダウンロードは拒否されます。10 月 8 日、Socket がそうするまでにかかったのは 11 分でした。
Strip preinstall / install / postinstall / prepare from npm tarballs on the fly を
オンにしていれば、プロキシが tarball を書き換え、"preinstall": "node lib/setup.mjs" の行は
npm が目にする前に消えます。その 1 行が、ワームの唯一の侵入口です。
それでも動いたとしたら
すべての層を外して、ペイロードを動かしてみます。ペイロードは VM のユーザーとして、マシン自身 のホームで動き、リストにあるものを探しに行きます。
トークンは身代わりです。 VM 内のどのファイル、環境変数、プロセスにも、本物の GitHub
トークン、クラウドのシークレット、SSH 秘密鍵はありません。GH_TOKEN と
~/.git-credentials には正しい形式の ghp_ トークンが入っていて、Mac 上のプロキシは
リクエストが GitHub へ出ていくときにだけ本物に差し替えます。~/.ssh に秘密鍵はありません。
SSH_AUTH_SOCK は Mac 上の ssh-agent への橋渡しで、ssh-agent は署名はしても鍵を渡すことは
ありません。Mac のホーム、キーチェーン、ブラウザーのプロファイルは、そもそも VM の中にあり
ません。ワームが暗号化して送り出すのは身代わりの束で、GitHub も AWS も Kubernetes も、その
プロキシを通さない限りどこでも拒否します。
外への出口は Mac を通ります。 Guardrails パネルで Unmatched traffic を
Deny にし、モデルプロバイダー、レジストリ、GitHub 向けのルールを置いたマシンは、
iseekaigogo.com にも 30 の Ethereum RPC エンドポイントのどれにも接続しません。設定に
かかわらず、こうした接続はすべて、どのマシンでも常に有効な Security Timeline に記録
されます。
書き込みはクリックを待ちます。 新しいマシンは、すべての git フォージで Prompt before
write を使います。ワームの GitHub への退避策は、あなたのアカウントに公開リポジトリを作る
ことです。これは POST /user/repos、つまり書き込みなので、macOS は
Allow write on "github.com" from workspace "…"? を、通信路に現れたままのリクエストと
一緒に表示します。拡散のためにあなたのリポジトリへ .claude/settings.json をコミットして
push すれば、同じダイアログが今度は POST /acme/app.git/git-receive-pack とともに出ます。
「Shai-Hulud: Here We Go Again.」という名前のリポジトリを作れとエージェントに頼んだ覚えは
ありません。Don't allow をクリックすれば、ワームの拡散はあなたの Mac で止まります。
人質は身代わり
gh-token-monitor ユニットは、それでも VM の ~/.config/systemd/user/ に自分を
インストールし、見つけたトークンで 60 秒ごとに api.github.com/user を呼び出し始めます。
そのトークンは身代わりです。プロキシが GitHub 向けに本物に差し替え、GitHub は 200 を返し、
モニターは待ち続けます。
これで対応の順番が逆になります。ノート PC では本物のトークンが攻撃者の手にあり、消去を
引き起こさずに、相手が使う前に失効させる競争になります。Bromure では本物のトークンが VM に
入ったことは一度もないので、攻撃者にはあなたと競う材料がありません。あなたのプロキシの外では、
攻撃者が持ち去った ghp_ の値を受け付けるものは何もないのです。
マシンをシャットダウンすれば、モニターも一緒に止まります。Rewind home… を開き、
インストール前の起動時のチェックポイントを選びます。Bromure は起動に成功するたびに
チェックポイントを取っています。ユニットファイル、~/.local/bin のスクリプト、マシンの
チェックアウトに仕込まれた .claude/settings.json は、すべて元どおりになります。直前の状態の
ホームはまず新しいロールバックポイントとして保存されるので、巻き戻し自体も取り消せます。
まっさらにしたいなら、Reset disk が署名済みのベースイメージからシステムディスクを
クローンし直します。
それでもスイッチが作動した場合、rm -rf ~/ が消すのは 1 台の VM の中の /home/ubuntu
です。Mac のホームディレクトリには手が届かず、Rewind home… で最後のチェックポイント
からマシンのホームを取り戻せます。
誰がインストールしたのか
こうしたインシデントで最初に問うのは、チームのどこかのマシンが問題のバージョンを取り込んだか どうかです。ノート PC なら、ロックファイル、npm のキャッシュ、シェル履歴を探し、ワームが痕跡 を消していないことを祈るしかありません。
Bromure では、サプライチェーンの層が有効かどうかにかかわらず、パッケージの取得はすべて
Security Timeline の 1 行になります。npm · [email protected] に、マシン、時刻、判定
が付きます。行は Mac 上に 90 日間保存され、VM の中からは何も編集できません。
Supply chain で絞り込み、バージョンを検索すれば答えが出ます。Export… で CSV に
保存して、インシデントのチケットに添付できます。bromure.io に登録された Mac では、同じ取得が
supply_chain.fetch イベントとして組織のイベントストリームにも送られるので、セキュリティ
チームは全開発者を対象に一度で問い合わせられます。
問題のバージョンは現れなかった
デフォルトで最低 2 日に設定された経過日数ゲートが、新しすぎるバージョンを npm の
バージョン一覧から取り除きます。10 月 8 日、npm install tensorlake は 0.5.143 に
解決されました。
盗まれたトークンは身代わりだった
GitHub、npm、クラウド、SSH の認証情報は Mac に残ります。ワームの人質は、Bromure の
プロキシを通したときにしか使えない ghp_ の値でした。
拡散にはあなたのクリックが必要
リポジトリの作成もコミットの push も書き込みです。新しいマシンのデフォルトである Prompt before write では、どちらも VM からは応答できない macOS のダイアログを待ちます。
消去されるのは VM 1 台
rm -rf ~/ が届くのはマシンのホームで、あなたのホームではありません。Rewind home が
前回の起動時に取ったチェックポイントから復元します。
次の定石
Shai-Hulud は今年何度も戻ってきており、その作者たちは、防御側が前回どう対処したかに合わせて 毎回バージョンを作り込んでいます。防御側は数時間でトークンを失効させてきたので、今回は失効を 罰するようにしました。次は別のことを罰するでしょう。クリーンアップスクリプトの実行、 LaunchAgent の削除、プロジェクトを再び開くこと。
ノート PC では、そのどの仕掛けも通用します。あなたが掃除しようとしているまさにそのマシンに、 攻撃者が本物の何かを握っているからです。Bromure のマシンでは、本物の認証情報、あなたの ホームディレクトリ、何が起きたかの記録が、すべてプロキシの向こうの Mac にあります。 掃除は巻き戻しであり、何かをローテーションする前に済ませられます。その背後にある通信路の 境界については、インストールはきれいだった と 脱出 1 回につき 4 万ドル をお読みください。