彼らはまず安全なバージョンを取り下げた
8月20日、何者かが汚染された arrayref を crates.io に公開し、その十六秒後、背後にあった安全なバージョンを次々と取り下げた。すると Cargo 自身の警告が、唯一残った新しいリリースを指し示すことになる。ペイロードはビルドスクリプトにあったので、プロジェクトを型チェックするだけで実行された。Bromure Agentic Coding はこの作戦全体を、攻撃側が必ず負けるストップウォッチの問題に変える。汚染されたクレートの寿命は 86 分、既定の年齢ゲートは二日だ。
攻撃者は、誰かを騙してマルウェアを入れさせる必要などなかった。きれいなバージョンを 消し去り、あとはレジストリ自身のアップグレード助言に任せただけだ。
8月20日 07:15:00 UTC、[email protected] が本物のメンテナのアカウントから crates.io に
現れた。その二十四秒後、攻撃者はバージョン 0.3.5 から 0.3.9 の yank を始める。背後の棚を
空にするための、十六秒のスクリプト作業だった。
yank は crates.io のリコール機構であり、リリースが壊れていたときにメンテナが引くレバー だ。Cargo はこれに、望みどおりの応じ方をする。ロックファイルにあるバージョンが取り下げ られたと警告し、新規に解決するときは yank されたバージョンを選ばない。そうした警告に続く 助言はすべて、更新せよと言う。
だから攻撃者は安全なバージョンを取り下げ、新しいリリースを一つだけ残し、責任ある行動を とることがそのままそこへ歩み込む道になるようにした。
九十分、最初から最後まで
Rust Security Response WG のアドバイザリ と StepSecurity が再構成したタイムライン は、攻撃者が二日かけて仕込んでいた作戦を描いている。
8月18日 01:25:58、彼らは crates.io に dtolney という名前のアカウントを登録した。エコ
システムの proc-macro クレートの大半に名前が載っている David Tolnay と、一文字違いだ。
その三十分後、proc-macro2 のタイポスクワットである、何もしないきれいな
[email protected] を公開する。あのリリースは、アカウントに履歴を与えるためだけに
存在した。
本命は五時間後に来た。[email protected] は8月20日 07:11:15 に、悪意ある build.rs
を携えて上がった。その四分後、[email protected] が正当な所有者のアカウントから出ていく。
変更点はただ一つ、proc-macro1 ^1.0.107 を指す新しい依存関係の行だ。Security Response
WG はここでメンテナについて慎重で、それは正しい。彼らは「arrayref の作者が悪意をもって
行動しているとは考えていない」とし、「その計算機か資格情報が侵害されている可能性が高い」
と記している。[email protected] が 07:34:07 に、[email protected] が 07:37:49 に
続いた。
そして、始まったときとほぼ同じ速さで終わった。Nextron Systems の研究チームが悪意ある
クレートを見つけ、報告した。crates.io は arrayref を 86 分後に、internment を 90 分後
に、append-only-vec を 107 分後に取り下げ、攻撃者が所有する六つのクレートを削除し、
侵害されたアカウントをロックした。RustSec は、誰かが汚染されたバージョンに対してビルド
した形跡は記録していない。
九十分は速い対応だ。それでも、その窓の内側でビルドを走らせていた人にとっては、対応は あとから来た。
ライブラリの中身は呼ばれる必要すらなかった
ペイロードはビルドスクリプトの中に住み、ビルドスクリプトはコンパイル時にあなたの権限で
走る。The Hacker News は、ペイロードが
cargo build、cargo check、cargo test
で発火したと報じている。真ん中のそれは慎重な選択肢だ。保存時にエディタが走らせるもの、
何も実行せずにコンパイラの意見だけが欲しいときに打つもの。ここでは、それで十分だった。
スクリプト自体は、目の前に置いてみれば短くて捻りもない。base64 の断片から
https://23.254.165[.]112:9089/ を組み立て直し、TLS 証明書の検証を切り、あなたの OS と
アーキテクチャに合ったペイロードをダウンロードし、切り離して実行する。気の利いた一手は
std::mem::forget(child) で、ハンドルを捨てることでプロセスを Cargo のジョブオブジェクト
から逃がす。インプラントは走り続け、コンパイラはグリーンのビルドで終了する。
次に降りてくるのはインフォスティーラーだ。
Wiz の分析
によれば、それは HTTPS POST で /49890878 にビーコンを送り、ホスト名、ユーザー名、OS、
インストール済みアプリケーションを集め、Chrome、Brave、Edge の SQLite ログインデータ
ベースに保存された資格情報を問い合わせる。永続化は Windows ではレジストリの Run キー、
macOS では LaunchAgent、Linux では systemd のユーザーサービス。C2 が沈黙すれば、五日ごと
に十個の .com ドメインを生成するドメイン生成アルゴリズムにフォールバックする。Wiz は
このインフラを北朝鮮の活動に結びつけている。Mastra の npm キャンペーンと同じ
/49890878 エンドポイント、共通の証明書発行者、そして同じ Hostwinds のレンジの反復利用だ。
出荷されたビルドは x86_64 の Linux、Windows、macOS に加えて aarch64 の macOS を覆う。 最後のそれはアップルシリコンのノートだ。誰かが、Rust をコンパイルするのは誰かを考えた のである。
あなたが選んだ覚えのない依存
arrayref の累計ダウンロードは 2 億 4500 万で、それを Cargo.toml に書いた人はほとんど
いない。StepSecurity は 406 の依存クレートバージョンを数えた。arrayref は winit の下
に、したがって egui と iced の下に、blake3 と blake2b_simd の下に、そして
Ethereum と Solana のツール群のかなりの部分の下に座っている。internment と
append-only-vec は二つ合わせてさらに 1900 万ダウンロードを加える。
だから、依存を追加する前にレビューせよという助言は、ここには届かない。誰も追加していない のだ。それは四階層下に、あなたが確かに追加したクレートの下に、流れ去るロックファイルの 差分の中に現れた。ライブラリのコードを読んでも助けにはならなかっただろう。汚染された 三つのクレートのソースには誰も手を触れていない。変更のすべては、二日前のクレートを名指す 依存関係の一行であり、公開したのは名前が正しく見えるアカウントだった。
コーディングエージェントのブログがこれを気にする理由
引き金をもう一度見てほしい。ビルド出力に yank の警告が出る。何かが cargo update を走ら
せてそれを消す。
その何かは、もう人間でないことが多い。警告を片づけるのは、あなたが頼んだことへ向かう道 すがらエージェントがやる家事だ。取り下げられたバージョンを見て、直し方を知っていて、 直しを当てる。あなたが眠っているあいだ、ループで、バックログをまたいで働く。ビルドが 失敗したからロックファイルを更新し、機能に必要だからクレートを足す。
無人で働くエージェントは、86 分の窓の内側にいた可能性が最も高い主体であり、見覚えのある 名前が公開した見慣れない推移的依存の前で立ち止まる可能性が最も低い主体でもある。侵害 されたメンテナのアカウント、David Tolnay と一文字違いのタイポスクワット、アップグレード を勧めるレジストリの警告。この三つを与えれば、判断が捕まえられるものは連鎖の中に何も 残らない。
それでも生き延びられる。そもそも判断は、これを決める場所として出来が悪いからだ。
Bromure はこれをストップウォッチの問題にする
Bromure Agentic Coding のプロファイルはすべて、年齢ゲートが二日に設定された状態で 出荷される。ホストの MITM プロキシは VM が行うすべてのパッケージ取得を、npm、PyPI、 Cargo、RubyGems、Maven、NuGet、Go モジュール、Packagist にわたって見ており、しきい値 より若いバージョンは返ってこない。
Cargo にはフルコースが用意されている。Bromure は index.crates.io、crates.io、
static.crates.io を認識し、通過するクレートのメタデータを書き換える。新しすぎる
バージョンをバージョン一覧から取り除き、max_version、max_stable_version、
newest_version を、フィルタを生き延びた中で最新のリリースに設定する。Cargo のリゾルバ
は、より新しいバージョンの存在を知ることがない。
では、数字を並べてみよう。
この作戦のどれ一つとして、二日の齢に達していない。[email protected] は arrayref
がそれを依存として名指したとき、生後四分だった。ゲートの向こう側のリゾルバからは
どちらのクレートも見えないので、cargo update は何も新しいものを見つけない。
それは画面に現れる。0.3.5 から 0.3.9 が yank され、0.3.10 が濾し取られた状態では、
arrayref の新規解決はバージョンを選べないまま返ってくることがある。それは静かな成功
ではなく、エラーだ。0.3.9 に固定された既存の Cargo.lock はビルドを続ける。yank が塞ぐの
は新規解決だけだからだ。どちらにせよ、あなたはその木曜の朝を、持っている資格情報を
すべてローテーションするのではなく、リゾルバのメッセージを読んで過ごす。
Bromure は二度目の確認もする。リゾルバがすでに答えを持っていた場合のためだ。すべての
アーティファクト取得、つまり .crate ファイルそのものを、記録された公開時刻と突き合わせ、
齢を名指す 451 を返す。41 分前に公開、ポリシーは最低 2 日を要求。温まったメタデータ
キャッシュは攻撃者に何ももたらさない。
ドロッパーの最初の一歩はどこにも届かない
時計を止めていたとしよう。誰かがパッケージを例外にする、あるいは一午後だけしきい値を
下げる。それでもビルドスクリプトは 23.254.165.112 のポート 9089 に届かねばならない。
Bromure のプロファイルはそれぞれ、pf 風の短いルールセットとして書かれた自前の送信ポリシー を持っている。
allow web crates.io
allow web static.crates.io
allow web index.crates.io
allow tcp api.anthropic.com:443
default deny
あなたの Mac は、一つのルールセットを共有する二つの層でこれを強制する。あらゆる
プロトコルにわたって宛先 IP と DNS から拾ったホスト名で照合する仮想スイッチと、TLS の SNI
と HTTP メソッドで照合するプロキシだ。ポート 9089 の裸の IP はそのファイルのどのルールにも
一致しないので default deny に落ち、接続は境界のホスト側で死ぬ。ステージ 2 の DGA、五日
ごとの十個の新しい .com ドメインも同じ壁にぶつかる。生成された名前も、誰もリストに載せ
なかった名前であることに変わりはないからだ。拒否はすべて egress.firewall の一行として
Window → Security Timeline に、ホスト、IP、ポート、判定とともに、VM の中のコードが
手を伸ばせない場所に書かれる。
そこに盗むものは何もない
この事件とともに配られた復旧チェックリストを読んでほしい。SSH 鍵、クラウドトークン、API トークン、署名鍵をローテーションする。レジストリのキャッシュを消す。CI のキャッシュ層を クリアする。vendor ディレクトリを作り直す。窓の内側でビルドしたすべてのマシンで。
Bromure のワークスペースは、そのリストの大半に空っぽの部屋で答える。
本物の資格情報は VM に入らない。そのゲストの ANTHROPIC_API_KEY は brm_… のプレース
ホルダーだ。kubeconfig には使い捨てのクライアント証明書が入っている。コンテナレジストリ
の認証は派生した Basic 文字列だ。GitHub トークンは偽物で、SSH 鍵はそのワークスペースの
ために生成されたプロファイルごとの ed25519 鍵であって、あなたのノートの鍵ではない。ホスト
の MITM プロキシが送信リクエストで本物の値に差し替え、AWS ではもう一段踏み込む。ホストが
本物の鍵材で SigV4 リクエストに署名し直すので、プロキシを迂回したものは動く呼び出しでは
なく、Amazon からの InvalidSignatureException を受け取る。
そのゲストを資格情報目当てに掃くインフォスティーラーは、資格情報を見つける。パースは 通るし、正しく見えるし、そしてマシンの外では何の価値もない。
買い物リストの残りは、ハイパーバイザの向こう側にある。Chrome、Brave、Edge はログイン
データベースをあなたの Mac の、ゲストからは経路のないユーザープロファイルに置いている。
書き込みたい macOS の LaunchAgent、aarch64 の macOS ビルドを同梱した理由そのものには、
書き込む先の macOS がない。ゲスト自身のホームに systemd のユーザーサービスを入れることは
できるが、その層にはボタンがある。Erase home… はプロファイルの /home/ubuntu を
クローン直後の状態に戻し、Reset to base… は読み取り専用のベースイメージから
ワークスペースのシステムディスクを再クローンする。
そして棚卸しはもう手元にある
この事件のどの是正ガイドも、最後の一行は grep だ。すべてのマシンのすべての Cargo.lock
を六つのクレート名と三つのバージョン文字列で検索し、それから ~/.cargo/registry/cache
を tarball で検索する。それは立派な指示であり、みじめな午後であり、そして手元に残って
いるマシンにしか効かない。
Bromure は起きたそばから答えを記録している。プロキシを通るパッケージ取得はすべて、
エコシステム、パッケージ、バージョン、結果、そして結果の理由を持つ supply_chain.fetch
の一行を、ホスト側の Window → Security Timeline に、資格情報の差し替えとファイア
ウォールの判定と並べて書く。次のアドバイザリがバージョンと二時間の窓を名指したとき、
「ここで何かがそれを取得したか」という問いは、遠征ではなく検索になる。
Bromure Enterprise Manager は同じストリームを艦隊規模でまとめ上げる。問いはたいてい その形で届く。夜通しエージェントを走らせていた九十台のうち、どれかがそれをビルドしたか。
残しておくに値する部分
crates.io はここでよくやった。報告から削除まで九十分、攻撃者のクレート六つを消し、侵害 されたアカウントをロックし、明快なアドバイザリを出し、被害の証拠はなし。Rust チームは さらに、資格情報を盗まれたメンテナを責めないよう気を配った。それは正しい本能であり、 いつも当たり前とは限らないものだ。
それでも誰かがこの仕組みを再利用するだろう。安上がりで、賢いからだ。yank は公開で、即時 で、一行のレバーであり、レジストリにアップグレードを勧めさせる。そして、どのエコシステム にも一つある。あなたのビルドを走らせる何かはその勧めを見て、それに従って動く。そして今、 それに従って動くものには、どのクレートがどれなのかについての木曜の朝ぶんの文脈が欠けて いることが多い。
だから、決定を文脈の要らない場所に置こう。バージョンの公開とあなたのビルドのあいだの 二日の日光は、パッケージやメンテナや一文字ずれたアカウント名についての判断ではない。 それは時計であり、この作戦には九十分しかなかった。
出典:Rust Security Response WG「Supply chain attack on arrayref」(2026年8月20日) · Wiz「Rust Supply Chain Attack on arrayref: Significant Overlap with DPRK Campaigns」(2026年8月20日) · StepSecurity「arrayref, internment, and append-only-vec Poisoned by the proc-macro1 Build-Time Dropper」(2026年8月20日) · The Hacker News「Rust Supply Chain Attack Puts Build-Time Malware in Crates with 245 Million Downloads」(2026年8月20日) · BleepingComputer「Hackers poison arrayref Rust crate to push infostealer malware」(2026年8月20日) · Semgrep「Rust crates arrayref & append-only-vec compromised」(2026年8月20日)