ワームがオープンソース化した
2026年5月11日週のどこかで、2025年9月以来メンテナアカウントを食い荒らし続けてきた自己複製型npmサプライチェーンワーム Shai-Hulud の背後にいる人物たちが、自らのソースコードをリークさせた。週末までに、OX Security は単一アカウント下にある4つのタイポスクワット npm パッケージを発見した。1つはリークされたワームのほぼそのままのコピー、もう1つは Golang 製の DDoS ボット、残り2つは SSH 鍵や暗号通貨ウォレットを格安の C2 へ送りつけるだけの単純なインフォスティーラーだった。サプライチェーン攻撃のフォークの敷居が一気に下がり、いまやこれらのパッケージをインストールする可能性が最も高いのは、もはや人間ではない。
先週のどこかで、2025年9月以来メンテナアカウントを噛み砕いてきた
npm ワーム Shai-Hulud の背後にいる人物たちが、自らのソースコード
をリークさせた。週末までに、deadcode09284814 という npm アカウ
ントがそのコードを再利用した4つのタイポスクワットを公開した。1つ
はほぼそのままのワーム。1つは Golang 製の DDoS ボット。2つは
SSH 鍵や ~/.aws/credentials、MetaMask のヴォルトを借りた IP に
POST するだけの単純なインフォスティーラーだ。2,678回のインストー
ルを経て、もはや問いは「誰かがリークを兵器化するか」ではない。問
いは、今日の午後あなたのターミナルで npm install をタイプして
いるエージェントのうち、どれがそれを掴むか、である。
攻撃用ソフトウェアでは時折こんなことが起きる。クローズドなツールが
どこかのフォーラムに投げ捨てられ、それを動かせる人々の数が「それを
書いた一団」から「VPSを持つどんな十代の若者でも」へと一変するのだ。
Mimikatz。EternalBlue。Conti のソース。そのたびごとに、能力そのも
のは変わらない――ただ希少ではなくなるだけだ。先週の見出しは、
OX Security
の調査をもとに
BleepingComputer
が報じたもので、6分間で42個の @tanstack パッケージを食ったときに
私たちが書いたワーム
Shai-Hulud が、そのリストに加わったというものだった。
フォークは机上の話ではない。日曜までに OX は、deadcode09284814
という単一の npm アカウントから公開された4つの悪意あるパッケージを
記録していた。
chalk-tempalte―chalk-templateのタイポスクワット。リーク された Shai-Hulud ソースをほぼそのまま搭載しており、盗んだ認証情 報を公開のGitHub自動生成リポジトリとしてアップロードするという Shai-Hulud の特徴的な挙動もそのまま含まれている。OXの見立てでは、 「Shai-Hulud のマルウェアコードはリークされたソースのほぼ完全な コピーであり、難読化テクニックは何もない」――つまり、シリアル番 号を削り落とす手間さえかけなかった新たな脅威アクターの存在を示唆 している。axois-utils―axios-utilsのタイポスクワットで、OX が Phantom Bot と呼ぶ Golang ペイロードを搭載している。HTTP、TCP、 UDP、リセットフラッド、Windows のスタートアップフォルダとスケジ ュールタスクによる永続化、そしてb94b6bcfa27554.lhr.lifeの C2。あなたの開発機が徴用されたわけだ。@deadcode09284814/axios-util― 別のタイポスクワットで、別の ペイロード。SSH 鍵、環境変数、AWS/GCP/Azure の認証情報を80.200.28.28:2222に送りつける。OXの言葉を借りれば「至ってシン プル」だ。color-style-utils― IP、地理位置情報、暗号通貨ウォレットのデ ータを取得してedcf8b03c84634.lhr.lifeに POST するだけの素朴 なインフォスティーラー。
OX の記事執筆時点での週間ダウンロード数の合計は2,678。
ここには別々の2つの物語が絡まっており、解きほぐす価値がある。退屈
な方は、npm にはタイポスクワットがある、という話だ。これはある。ず
っとあったし、これからもある。それはちょうど犬の公園に Bench とい
う名前の犬がいるのと同じ理由――名前は安く、名前空間は平らだから
だ。興味深い方は、Shai-Hulud 級のワームを動かす敷居が一気に下が
ったという話である。先週まで、必要だったのはオリジナル一団のツール
群、オリジナル一団のインフラ、オリジナル一団の「捕まらないための規
律」だった。今日必要なのは、公開リポジトリの GitHub クローン、
lhr.life トンネル、そして npm publish をタイプする忍耐だけだ。
OX が見つけた4つのパッケージは、まとめてその証拠である。
なぜパッケージ数よりアクター数の方が重要か。
単一の洗練されたワームは、ある意味では扱いやすい敵だ。手癖がある。
インフラがある。習慣がある。検出シグネチャを書くこともできる。
Aikido、Socket、Snyk、Wiz――5月11日の @tanstack インシデントに飛
び乗った npm サプライチェーン監視業者たち――は、まさに同じファミリ
ーを8か月間追い続けていたからこそ、数時間以内に捕まえることができ
た。
これに対し、ペーストサイトからソースをダウンロードした人々が書いた
派生ワームのファミリーはまた別の形をしている。それぞれが異なる
C2 に流出させ、異なる RSA 鍵を埋め込み、読み取るファイルの組み合わ
せを変え、棲み着くタイポスクワット領域を変えてくる。注意深いものも
あるだろう。多くは雑なやり方ですぐに捕まるだろう。そのうちのひとつ
は、私たちが次にこんな記事を書くとき、1週間では捕まらないほどに洗
練されているだろう。防御側の検出問題は、「Shai-Hulud を見つけ出す」
から「prepare スクリプトの中から ~/.ssh/id_ed25519 を読もうと
するあらゆるものを見つけ出す」へと広がる。これははるかに、はるかに
大きな対象面だ。
インストール経路の形も変わった。これこそ、コーディングエージェント
を動かしている人なら誰もが心配すべき部分である。2024年に
chalk-template を欲しがった人間の開発者は、チュートリアルからパ
ッケージ名を読み、それをタイプし、chalk-tempalte がダウンロード
数200で見知らぬ人物を公開者として返ってきたときにタイポに気づいた
ものだ。2026年に「自分のCLI出力に色をつけてほしい」と頼まれた
コーディングエージェント
は、パッケージマネージャが返してきたものを何であろうとインストール
する。エージェントは公開者フィールドを見ない。エージェントは
README が3行しかないことに気づかない。エージェントは今この時間に30
回 npm install を走らせている。なぜなら、ユーザーは小さなチーム
が行うはずの仕事をしており、エージェントの料金はインストール単位で
はなくタスク単位で支払われているからだ。
難読化なしの Shai-Hulud クローンが実際にすること。
chalk-tempalte パッケージは、OX 曰く、リーク元のソースから「ほぼ
変更なし」である。つまり、私たちが
The worm that writes itself into .claude
で歩み解いたのと同じメカニクスが当てはまる――ただし、ひとつ大きな
新しい捻りがある。流出チャネルが GitHub そのものなのだ。
Shai-Hulud の特徴的なトリック――書き直すよりコピーする方が楽なため、
模倣犯にも保持された――は、盗んだ認証情報が隠れたドロップサーバー
に送られないことだ。それらは、マルウェアがたった今被害者から盗んだ
GitHub トークンで作成された、新たに作られた公開の GitHub リポジ
トリに送られる。被害者の機密情報は、被害者自身の GitHub アカウント
が所有する公開リポジトリに、誰かが気づいてリポジトリが消されるまで
の間、世界中の誰でもスクレイプできる状態で置かれる。防御側の標準的
な対応手順――流出先ドメインをブロックする、異常な外向き DNS を探す
――では、これは捕まえられない。なぜなら外向き DNS は
api.github.com であり、あなたの開発機はとにかく毎時200回はそれと
喋っているからだ。認証情報の束は、git push に変装してあなたのラ
ップトップを後にする。
機密情報が一度公開されれば、GitHub の消火栓を見張っている人なら誰 でも――そしてまさにこの理由で GitHub の消火栓を見張っている人は何 人もいる――それを掬い上げられる。ワームは C2 を生かしておく必要も ない。GitHub が C2 なのだ。
この絵には、立ち止まって眺める価値がある小さなディテールが含まれて
いる。Shai-Hulud クローンの chalk-tempalte は、戦利品を受け取る
ために自前のインフラに頼ってさえいない。それは、被害者自身の
GitHub アイデンティティを使って、被害者自身の盗まれた機密情報を
GitHub 上の公開リポジトリへ発行する。lhr.life の C2 はバックア
ップだ。主チャネルは git push である。出口を監視する防御側にとっ
ては、これは開発者のいつもの CI と区別がつかない。GitHub にとって
は――誰かがリポジトリを通報するまで――それは実在ユーザーが所有す
る正当な公開プロジェクトに見える。流出は、被害者のアイデンティティ
を通じて洗浄されるのだ。
あなたがスクロールしている間に、エージェントがしていること。
あなたのターミナルにコーディングエージェント――Claude Code、
Cursor's CLI、Codex CLI、Aider、どれでもいい――がいるなら、最後の
段落を読んでいる間に、エージェントがあなたの代わりに npm install
を1回走らせている可能性はゼロではない。たぶん2回かもしれない。コー
ディングエージェントは依存ツリーに見惚れて立ち止まったりはしない。
そもそもあなたがそれを買った理由は、それが立ち止まらないことだか
らだ。
OX が掴まえたパッケージはタイポスクワットで、これはまさに機械の速
度に特に適した種類のミスである。chalk-template をタイプしなけれ
ばならない人間は、100回もタイプしてきたから文字を正しい順序で並べ
る。地球上のあらゆる Stack Overflow の投稿を取り込んだモデルは、同
じ訓練コーパスの中で chalk-template と chalk-tempalte を見てき
ており――後者はたいてい他の誰かのミスを撮ったスクリーンショットの
中で――「CLIにカラー出力を追加して」というプロンプトを与えると、
時としてタイポをそのまま吐き出す。エージェントはひるまない。パッケ
ージマネージャもひるまない。prepare スクリプトは走る。
これは仮想の故障モードではない。Shai-Hulud ファミリーがそのために 設計された故障モードだ。元のワームは、メンテナのトークンを盗み、 それを使って正当に人気のパッケージの侵害バージョンを再公開すること で広まった。模倣犯たちはまだメンテナトークンを持っていない。彼らが 持っているのはタイポスクワットの名前空間であり、エージェントはそこ に落ち込むのが恐ろしく得意なのだ。
この物語の中で Bromure が座る位置。
Bromure Agentic Coding は、コーディングエージェ
ントを使い捨ての Linux VM の中で、タスクごとに走らせる構成だ。プ
ロジェクトフォルダはマウントされ、出口はブローカーされ、認証情報は
ハイパーバイザーの macOS ホスト側に保管される。アーキテクチャの詳
細は
Bitwarden CLI の記事
と
@tanstack の記事
で詳しく歩み解いた。以下では、この4つのパッケージそれぞれが、その
境界の内側で具体的に何に出会うかを見ていく。
これを4つのパッケージに対して、ひとつずつ突き合わせていこう。アー キテクチャがその対価を稼ぐのは、こうした具体例の中だからだ。
chalk-tempalte が $GH_TOKEN に手を伸ばす。
Shai-Hulud クローンの十八番は、開発者の GitHub トークンを取り、そ
れを使って開発者自身のアカウント上に公開リポジトリを作ることだ。
Bromure VM の内側では、それが読む $GH_TOKEN はスタブ――ghp_ で
始まる構文的に正しい文字列で、まさにこの目的のためだけに存在する
――である。ランナーの最初の動作は api.github.com への
POST /user/repos だ。ホスト側の出口プロキシは api.github.com を
ホワイトリスト済みのエンドポイントとして認識するが、それは現在のタ
スクが実際に要求した操作についてだけだ――作業中のリポジトリへの
git push、同じリポジトリに対する gh pr create、
gh api repos/that/repo/issues。「ユーザーのアカウント上に新しい公
開リポジトリを作る」はそのリストにない。ユーザーがそれを頼んでいな
いからだ。プロキシは本物のトークンへの差し替えを拒否し、スタブはス
タブのまま外に出る。GitHub は 401 を返す。ワームの流出チャネル――
DNS出口フィルタリングを回避するために設計された巧妙なチャネル―
―は開かれることがない。
予備チャネルである 87e0bbc636999b.lhr.life の lhr.life トンネ
ルもホワイトリストに入っていない。トレースは試行を記録する。バイト
は外に出ない。
axois-utils が永続化のため Phantom Bot をインストールする。
Golang ボットは自らを Windows のスタートアップフォルダに書き込み、
スケジュールタスクを作成しようとする。Bromure VM は Linux ゲストな
ので、Windows 固有の永続化はおまけで no-op になる。同じペイロード
の Linux 版――誰かが間もなく出荷するだろう――では、ボットは
/etc/cron.d/ か ~/.config/systemd/user/ に書き込むことになる。
どちらのパスもゲストの使い捨てコピーオンライトディスクの中にある。
次の bromure reset、あるいは現在のタスクの自然な終了で、そのディ
スクは破棄される。永続化は探すまでもなく消える。
一方、ボットの b94b6bcfa27554.lhr.life への外向き接続は、タスク
の出口ホワイトリストに入っていない。正当なコーディングタスクが新規
登録された lhr.life トンネルと喋ることはないからだ。ボットは閉じ
たソケットに電話をかける。セッショントレースはその試行を記録する―
―明日の朝 IOC リストが公開されたとき、これは役に立つ。
@deadcode09284814/axios-util が生の認証情報を POST する。
4つのうち最も単純なペイロードは、掴めるものも最も少ない。ランナー
はゲストの ~/.ssh、~/.aws、環境変数を読み、80.200.28.28:2222
に POST する。SSH ディレクトリは空。AWS ファイルはスタブ。環境変数
はスタブか未設定だ。送信先 IP はホワイトリストにない。接続はプロキ
シで遮断されるか、あるいはプレースホルダのペイロードを抱えてホスト
を離れる。どちらの結果でも構わない。
color-style-utils がそこにないウォレットを探す。
クリプトスティーラーは、4つの中で開発者自身のブラウザが同じマシン
上にあることを最も明確に前提とするパッケージだ。これは
~/Library/Application Support/Google/Chrome/Default/Local Extension Settings/<MetaMask-id>/
や Phantom と Keplr の同等のパスを読む。Bromure VM 上にはそれらの
パスはどれも存在しない。VM の中にあなたの Chrome プロファイルは
入っていない。VM にはウォレット拡張がインストールされていない。VM
は設計上、誰のメインブラウザでもない。ランナーは空のディレクトリを
見つけて先に進む。
これは、認証情報がホスト上に存在することの物語ではない部分だ。ウ ォレットがホスト上に存在するのは、普通のラップトップでは、開発者の ブラウザと開発者のコーディングエージェントが同じファイルシステムを 共有しているからだ。Bromure はウォレットを強くしているわけではない ――ウォレットを、ワームが動いている場所から到達できなくしているの だ。ワームは自分のディスクにないものを読むことはできない。
それでも痛む箇所。
この物語には、Bromure のタスク単位 VM が解決策にならない場所もあ る。それは声に出して名指しする価値がある。
プロジェクトフォルダはマウントされている。
ワームがプロジェクトフォルダに書き込んだファイル――@tanstack
の記事で取り上げた .claude/router_runtime.js 型の永続化を含む
――はタスクのリセットを越えて残る。それがプロジェクトフォルダ
をマウントする目的そのものだからだ。ここでの防御は VM ではな
い。それは git status と、push する前に5秒だけ diff を眺める
ことだ。トレースを見れば、どのセッションが想定外のファイルを追
加したかを見つけやすくなる。
出口ホワイトリストは意図的に狭い。
Bromure の認証情報ブローカーが機能するのは、ホワイトリストが狭
いからだ。今日リリースを発行するからと自分のスコープに対する
npm publish をホワイトリストに入れ、たまたまその日にこの4つの
パッケージのうち1つをインストールすると、ワームはあなたのスコ
ープ下に発行する。タスクに必要なものだけをホワイトリストに入れ
ること。それ以上は1バイトたりとも入れないこと。
このタスクに有効なトークンも、依然として本物のトークン。
タスクがホワイトリストに入れた操作については、スタブの GitHub
トークンはワイヤー上で本物に差し替えられる。もし
chalk-tempalte がエージェントを説得してプロジェクト自身のリポ
ジトリに git push させられたら、その push は本物のトークンで
通る。境界は認証情報を守る。それは diff をレビューはしない。
diff を読むこと。
検出はトレースの下流にある。
セッショントレースはあらゆるシェルコマンド、ファイル書き込み、
外向きリクエストを記録する。それ単体では 87e0bbc636999b.lhr.life
を悪いものと分類はしない。リクエストが行われたことを記録するだ
けだ。OX が明日の朝に新しい IOC リストを公開したとき、あなたの
検索は2秒で済む。それがトレースの加える価値だ――魔法ではなく、
ただの領収書である。
最後にひとつ。
ニュースはリークではない。ニュースは、リークが今後1年でもたらすであ
ろうこと――洗練された形ですら npm エコシステムがかろうじて間に合っ
たあのワームの、半端な能力で作られた小さなフォークがたくさん出てく
る、ということ――だ。一部のフォークは記事になるほど騒がしいだろ
う。多くはレジストリで1週間ほど座って、数千の npm install を集
め、誰かがやっと不正報告を出すころには消えるだろう。OX が
deadcode09284814 パッケージで計測した2,678インストールは外れ値で
はない。それが平均なのだ。
正直な問いは「私のチームは npm のあらゆる汚染されたタイポスクワッ トを避けられるか」ではない。エージェントは速く打つ。名前は安い。正 直な問いはこれだ――エージェントが1つをインストールしたとき――そし て今後1年で、エージェントを使うチームでは、いずれそうなる――その postinstall スクリプトは、開発者の手を見つけるのか、それとも、認証 情報ファイルにスタブが入り、自分が誰かも分からないプロキシを備え た、使い捨ての Linux ボックスを見つけるのか。
Bromure Agentic Coding は後者だ。無料で、オープ ンソースで、本日出荷済み。フォークの木は、良くなる前にもっと悪くな る。