インストールはきれいだった
npm は 7 月、依存パッケージのインストールスクリプトを既定で実行しなくなった。9 月 17 日、Checkmarx はその判断と争わないキャンペーンを公開した。ローダーは `BTree.prototype.set` の中、つまりそのライブラリが呼ばれるために存在するメソッドの中に座り、値 100 のキーが最初に格納された瞬間に発火する。週二百万ダウンロードの上で十一週間動き続け、その指令チャネルは Ethereum Sepolia 上のスマートコントラクトだった。コーディングエージェントは理想的な被害者である。コードを動かすことこそが仕事だからだ。Bromure Agentic Coding は、そのコードが何に手を伸ばせるかを、動き出す前に決める。
インストールスクリプトを止めたのは正しい判断で、npm のエコシステムは 7 月にそれを 下した。このキャンペーンは実行の瞬間をパッケージの初回使用へ移した。インストールは きれいなままで、引き金を引くのはあなたのテストスイートだ。
あなたのエージェントは順序付きマップを必要とする。npm から b-tree のパッケージを
選び、package.json に加え、インストールする。何も走らない。npm 12 は依存パッケージ
のインストールスクリプトをもう実行しないので、postinstall もなく、preinstall も
なく、ビルドフックもなく、ライフサイクルスクリプトのポリシーが拒む対象そのものが
ない。エージェントは新しい依存を使うコードを書き、数百行を挿入するテストを書き、
それを走らせる。
百回目の挿入が、二つ目の Node プロセスを起動する。
Checkmarx は 9 月 17 日にこのキャンペーンを公開した。
調査は Bruno Dias による。
BleepingComputer は 20 日に、
InfoWorld は 21 日に
これを報じた。パッケージ名は indexed-btree。本物の sorted-btree と見間違えられる
ように作られており、npm が 9 月 3 日に削除した時点で週二百万近いダウンロードを集めて
いた。悪性の形のまま、およそ十一週間公開されていたことになる。
誰もが築き終えたばかりの防御
6 月に npm の CLI はインストールスクリプトの既定値を反転させ、
npm 12 が 7 月にその変更を出荷した。
依存パッケージの preinstall、install、postinstall は、プロジェクトが名指しで
許可しない限り走らなくなり、git や リモート URL の依存も既定では解決されなくなった。
その記事が引く JFrog の数字は、この三つの経路を前年に観測された悪性 npm パッケージの
およそ 53% に位置づけている。エージェントのハーネスも同じ姿勢を取った。
--ignore-scripts、あるいはパッケージマネージャが見る前に tarball からフックを
取り除くプロキシである。削除日から十一週間さかのぼると、indexed-btree の最初の
悪性バージョンはちょうどこの展開の真ん中に落ちる。
そこで運用者はインストールスクリプトを使うのをやめた。ローダーは
BTree.prototype.set の中、つまりそのライブラリが呼ばれるために存在するメソッドの
中に住む。渡されたキーが 100 のとき、コードは extended/sharedLoad.min.js から
難読化された第一段ローダーを切り離された Node プロセスとして起動する。Checkmarx の
まとめは、これが予見しにくい理由を正確に言い当てている。ローダーはライブラリ自身の
メソッドの内側に隠れており、それは彼らの言葉で言えば「標準的なテイント解析ツールを
すり抜けるよう、よくできた手口」である。
何がこれを武装させるかを見てほしい。値 100 のキーだ。キーを保持することを目的とする ライブラリの中で、である。ランキング表がそこに届く。ページ分割されたインデックスが 届く。行をループで挿入するテストは、百回目の反復で届く。README の言うとおりに パッケージを使えば、あなたがそれを武装させる。
十一週間と、問題なく読めるリポジトリ
残りの手口は、あなた自身が行うはずの確認を狙っている。
指令チャネルは Ethereum Sepolia 上のスマートコントラクトだ。ローダーはそこから次の アドレスを読み、X25519 の鍵ペアを生成し、コントラクトから運用者の公開鍵を取り、ECDH で AES 鍵を導き、チェーン上に置かれた二つの暗号文を復号して第二段を組み立てる。 Checkmarx はその利点を率直に述べている。「この技法は従来の C2 手法よりもドメインや IP のテイクダウンに強い。古いアドレスが潰されるたびに、スマートコントラクトが新しい アドレスへのポインタとして働くからだ」。そこには、ブロックリストに入れるべき不正な ドメインは一つも出てこない。手に入るのは公開 RPC のエンドポイントとコントラクトの アドレスだけで、運用者はインストール済みのパッケージに一切触れずにコントラクトの 向き先を変えられる。
プロジェクトは本物に見えた。運用者はもっともらしいコミット履歴を持つ GitHub リポジトリと、それに見合う開発者アカウントを用意した。そしてそのリポジトリのコードに 悪性の部分は含まれていない。それがあるのは npm の tarball の中だけだ。GitHub で ソースを読むという、誰もが勧める助言は、このパッケージが問題ないと裏づけただけ だったはずである。
日数も彼らの味方をした。十一週間もあれば、あなたがインストールする頃にはそれは
定着した依存になっている。数時間で撤回される乗っ取りリリースとはまるで違う。
Checkmarx は同じ挙動を持つ兄弟パッケージを九つ数え、btree-core だけで 190 万
ダウンロードを超え、他も各々数十万に達していたとし、運用者は 109 ETH を保持する
ウォレットを握っているとしている。
そして第二段が着地したあと、ローダーは自分のファイルを消し、プロトタイプから引き金を 取り除く。ディスク上のパッケージは、ただの b-tree ライブラリの姿に戻る。
コードを動かすことが仕事である
上のどの防御も、人がなにかを実行すると決める瞬間を前提にしている。エージェントは その瞬間を取り去る。
コーディングエージェントは依存をインストールし、それを使うコードを書き、テスト スイートを走らせ、失敗を読み、直し、また走らせる。一時間に何十回も、あなたが会議に 出ている間に。そのループこそが価値の全てだからだ。あなたは百回目の挿入を読んでは いない。しかもエージェントには、この引き金を踏む種類のデータを生む理由があなたより 多い。フィクスチャをループで書き、連番 ID でテーブルを埋め、数千行でベンチマークを 取る。武装条件が「誰かがキー 100 を格納する」ことなら、エージェントは人より早く、 人より何度もそこへ到達する。セッションが抱えるものをすべて抱えたプロセスの中で。
インストール時の防御は、パッケージが到着する瞬間を覆う。あなたのエージェントが そこで過ごすのは一日一分で、残りはすべて、到着したものを動かすことに費やされる。
ローダーが必要とするもの
キャンペーンを骨まで削ぎ落とせば、必要なのは二つだけだ。届く先と、目覚めた機械の上に ある持ち去る価値のあるもの。その両方を、動き出す前にあなたが決められる。そしてどちらの 決定にも、パッケージを見分ける必要はない。
開発者のノート PC では、両方が攻撃者に有利に働く。送られる偵察情報 —— ホスト名、
アーキテクチャ、CPU、メモリ、稼働時間 —— は、あなたのネットワーク上のあなたの機械を
描く。生成されるプロセスはあなたとして走る。あなたのシェル、あなたの
~/.git-credentials、あなたの GH_TOKEN、あなたの ~/.aws、あなたの SSH
エージェントを伴って。宛先は slack.com、api.telegram.org、そして公開の Ethereum
RPC プロバイダの三つ。あなたのツールの半分が既に話しかけている SaaS のホストが二つと、
インフラに見えるブロックチェーンのエンドポイントが一つだ。netflow のログで、この一覧は
どれも悪性には読めない。
Bromure Agentic Coding は、この二つの半分の両方に、エージェントが走る VM の外、 ホスト側で答える。
まず資格情報だ。そこが標的だからである。あなたが設定した秘密はすべて、ワークスペースの
VM の中では構造を保った代替値として存在する(GitHub のトークンなら ghp_ に 36
文字、Anthropic のキーなら sk-ant-api03-brm- で始まる)。本物の値から HKDF-SHA256 と
インストールごとのソルトで導かれるので、gh も git も claude もそれを受け入れ、
セッションをまたいで値が回転したようにも見えない。本物の値はあなたの Mac 上で暗号化
されたまま残り、ホストのプロキシが、要求が VM を出たあとに、その資格情報が発行された
ホスト宛てのときに限って、回線上で差し替える。マニュアルは不変条件をこう述べる。VM の
中のいかなるファイル、環境変数、プロセスも、本物の API キー、OAuth トークン、AWS の
シークレット、SSH 秘密鍵を決して含まない。環境を読むスティーラーが集めるのは代替値だ。
代替値を、それが属さない場所へ持ち出すこと自体が警報になる。プロキシは外向きの要求を すべて走査し、発行先でないホストへ向かう偽物を探す。見つかればプロキシはその要求を 451 で遮断し、宛先には何も届かず、Bromure は VM を一時停止する。切り離されたセッションを 強制的に再接続し、枠を赤く染め、トークンのプレビュー、資格情報、それが属していた ホスト、向かおうとしていたホストを名指しするアラートを上げる。ワークスペースを落とす ことも、Save for Investigation を選ぶこともできる。後者はシステムディスク、ホーム ディレクトリ、共有フォルダのすべてを書き出してから停止する。このローダーはハンド シェイクのあとに自分のファイルを消すので、後から見た者はきれいに片づいたディスクを 見つけるだけだ。要求の途中で止めれば、片づける前の機械が手に入る。
次に届く先だ。ガードレールはワークスペースごとに送信ファイアウォールを備える。 VM が開くすべての接続に対する pf 風の順序付きルールセットで、ホスト、CIDR、 プロトコル、ポート、そして Web ルールでは個々の HTTP メソッドまで指定でき、 Unmatched traffic を Allow か Deny に設定できる。これを Deny にし、 仕事に必要なレジストリと API を並べておけば、Telegram のボット API や Ethereum の RPC プロバイダへ手を伸ばすローダーは、回線上で拒絶に出会う。ゲストがどちらも迂回できない よう、二つの層がこれを適用する。仮想スイッチは宛先 IP と DNS から読み取ったホスト名で TCP と UDP の両方を照合し、プロキシは TLS のサーバー名とメソッドで再び照合する。 スイッチは VM のポート 80 と 443 のフローをプロキシ自身へ引き込むので、ペイロードが 解除できるプロキシ変数というものが存在しない。判定はすべて Security Timeline の Firewall 行として残り、ルールの変更は再起動なしに実行中のセッションへ届く。
暦の届かないところ
Bromure の公開日数ゲートは、既定で二日に設定されており、乗っ取りの型のために存在する。 メンテナのアカウントが侵害され、汚染されたバージョンが公開され、その全体が数時間で 見つかって撤回される、という型だ。このゲートは、そうした事件を何度も受け止めてきた。 十一週間はあなたが設定するどんな閾値も越えてしまうので、ここでの仕事は他の層に回る。
Supply Chain のペインが積み重ねになっているのは、そのためだ。最初から悪性だった パッケージのために作られた層がパッケージフィルタリングである。socket.dev の 侵害パッケージ遮断は、マルウェアとして印を付けられたパッケージ、タイポスクワット、 怪しいインストールスクリプト、難読化されたコードを覆い、Depi は npm レジストリを 事前検証済みのミラーに置き換える。どちらもホスト側で動き、API キーはあなたの Mac に 留まって VM へ書き出されることはなく、どちらもインストールの後ろではなく前に立つ。 遮断は HTTP 451 として返り、その平文の本文を npm がそのまま印字するので、エージェントは 理由を読み、固まる代わりに回り道をする。
どのレピュテーションサービスもすべてを捕まえはしないし、十一週間きれいに動いた タイポスクワットはそのどれにとっても難しい事例だ。だからこそ、残りの層が自分の場所を 稼ぐ。代替の資格情報、侵害検知、送信ルールセットは、インストールされたパッケージが 何に届くかを決めるのであって、そのどれもパッケージを見分ける必要がない。それらは、 まだ誰も分類していないコードを覆う。これから十一週間、誰も分類しないコードも含めて。
初回使用で発火すると考える
ライフサイクルスクリプトの遮断はやる価値があり、このキャンペーンはそれを回り道 した。テストスイートがライブラリを呼んだ瞬間に目覚めるペイロードを前提に計画する こと。
引き金は正当なデータである
キーと値のライブラリにおける値 100 のキー。入力の許可リストは役に立たず、 エージェントはそのデータをあなたより速く生む。
プロセスに盗むものを残さない
VM を描く偵察情報と、上流では何の価値もないトークン。それは実行の成功を、無駄に 終わった実行に変える。
宛先は前もって決めておく
Slack、Telegram、公開の Ethereum RPC エンドポイントはレピュテーションでは遮断 できない。許可リストなら、それらを見分ける必要がない。
インストールスクリプトの遮断は、悪性 npm パッケージのおよそ半分を盤上から取り除いた。
だからこそ運用者は、偽のリポジトリ、算術に偽装した引き金、ブロックチェーン上の指令
チャネルという、これほど手の込んだものを組み立てる必要があった。一年前なら
package.json の四行で済んだコードを置くために、である。防御は効いた。そして攻撃の
値段は上がった。
その下にある前提だけが、一度も動いていない。あなたは依存をインストールしたらもう安全と 扱い、それを動かすことを中立な行為と見なしている。コーディングエージェントにとって、 それを動かすことが仕事だ。一日に何千回も、部屋には誰もいないままで。そのプロセスが何を 抱え、どこへつなげるかを動き出す前に決めておけば、あなたのエージェントの百回目の挿入は、 Security Timeline の遮断された Firewall 行として終わる。