すべての投稿に戻る
公開日 · 著者 Renaud Deraison

変えられたのはアドレスであって、パッケージではない

2026 年 8 月 31 日の 14 時間、registry.coder.com への一部のリクエストに応答していたのは、攻撃者が Coder 自身の Cloudflare オリジンプールに追加したサーバーでした。ホスト名は正しく、証明書は検証を通り、モジュールはいつもどおりの名前とバージョンを持ち、そして返ってきたコードは SSH 鍵と OIDC トークンとデータベースのパスワードを探し回りました。Coder は攻撃者側のログを持っていないため、アドバイザリは読者自身の外向き通信を調べるよう求めています。コーディングエージェントを動かしているマシンの大半は、その問いに答えられません。

ホスト名は Coder のものでした。証明書は検証を通りました。モジュールは、あなたの テンプレートがすでに固定していた名前とバージョンを持っていました。8 月 31 日の 14 時間、応答していたサーバーの一部は別人のものでした。

あるプラットフォームエンジニアが、月曜の朝にワークスペーステンプレートを更新 します。terraform init が、テンプレートの指定するモジュール(Docker、Zed、 aider)を registry.coder.com から取得します。先月と同じ場所です。Terraform は いつもの緑の壁を出力し、ビルドは成功します。その朝も、その月も、どのツールも何も 警告しません。

9 月 1 日、Coder は GHSA-vx42-ghc9-gw65 を公開しました。深刻度は CVSS 9.0 のクリティカルです。その言葉では、「身元不明の 悪意ある行為者が Coder の Cloudflare インフラへのアクセスを取得し、Coder の モジュールレジストリが使用するプールに未承認の IP アドレスを追加した」。8 月 31 日(月)の 07:35 から 21:45 UTC のあいだ、Coder モジュールへの一部のリクエストは 攻撃者が制御するマシンによって応答されました。Coder はセルフホスト型のクラウド 開発環境を作っており、 BleepingComputer は その利用者として Dropbox、Palantir、Square、Mercedes-Benz、KKR、EnBW、米政府 機関、防衛関連企業を挙げています。

誰もパッケージには触れていない

慎重なチームが持つサプライチェーンの習慣は、すべて成果物への確認です。名前の 綴りは正しいか。公開者は名乗るとおりの相手か。このバージョンは昨日公開されたのか。 ロックファイルのハッシュはまだ一致するか。上げる前に差分を読む。

攻撃者が変えたのは、クライアントがアドレスを尋ねたときにどのマシンが答えるかで あって、成果物そのものは放置しました。パッケージは一つも公開せず、メンテナ アカウントも乗っ取らず、そっくりな名前も登録せず、Coder のレジストリ内の何も 編集していません。名前はいつもどおりに解決しました。TLS ハンドシェイクは、本物の ホスト名に対する有効な証明書を相手に完了しました。モジュールの名前は docker であり zed であり aider でした。それが Coder のテンプレートが要求する モジュールだからです。そしてテンプレートがすでに固定していたバージョン番号を 持っていました。

違っていたのは返ってきた中身です。汚染されたコピーには dlp.sh という名前の シェルスクリプトが同梱され、docker、aider、rstudio-server、windows-rdp、zed の 各モジュール向けに専用のビルドがあり、アドバイザリにはそれぞれのハッシュが載って います。アドバイザリはそのペイロードを、「いくつもの資格情報を特定し、Coder が 日常的に使うドメインによく似た名前のリモートサーバーへそれらを持ち出すよう設計 された」コードだと説明しています。そのドメインが coder-infra[.]com で、8 月 28 日、誰かが使う三日前に登録され、199.91.220.205 に解決し、折り返し先は http://www[.]coder-infra[.]com/cli/check でした。

何を狙ったかの一覧は、開発プラットフォームの信頼の棚卸しのように読めます。 プロビジョナの環境変数と機密、ユーザーの OIDC トークン、SSH 鍵、外部認証の資格 情報、そしてデータベースのパスワードや認証プロバイダの資格情報を含む coderd の 設定変数。

その窓に入るのに、変わったことをする必要はありませんでした。アドバイザリいわく、 「Coder の主要モジュールレジストリ(registry.coder.com)をテンプレートのソースと して使用しており、このコードが提供されていた期間中にコンポーネントを更新した ユーザーは、悪意あるパッケージをインストールした可能性がある」。テンプレートを 作る、テンプレートのバージョンを上げる、ドライランを回す、モジュールキャッシュを 切ってワークスペースをビルドする。どれも普通の月曜の仕事です。

8 月 31 日 07:35–21:45 UTC — 14 時間の窓ビルドが要求するterraform init新しいテンプレート、版上げ、ドライラン名前が解決するregistry.coder.comCloudflare、本物の正面玄関プール内の IP攻撃者が追加し、改変版を配信するdlp.sh折り返し先coder-infra[.]com8/28 に登録、8/31 に使用クライアントが確認したもの、そして見つけたものホスト名Coder 自身のもの、偽装ドメインではない証明書本物の名前に対して有効、本物のエッジでモジュール名docker, zed, aider —要求したものそのものバージョンテンプレートに既にあった番号。新規なし四つの確認はすべて通る。どのサーバーが答えたかだけは、クライアント側のどの確認も見ない。
攻撃者は registry.coder.com の背後にあるオリジンプールに自分の IP アドレスを追加しました。だからホスト名も証明書もモジュール名も固定されたバージョンもすべて本物のまま、バイト列だけが別の場所から来ました。汚染されたビルドが正常なビルドと違う点は、観測できる形では一つだけ。三日前に登録されたドメインへの外向き接続です。

「ファイアウォール、プロキシ、DNS、VPC フローログを確認せよ」

これが Coder の是正手順の五番目で、アドバイザリの中で最も示唆に富む一行です。 手順一から四は Coder が手伝えることです。窓の期間にキャッシュされたモジュールを 見つける SQL、それを消す SQL、修正版リリース(2.37.0、2.36.4、2.35.7、2.34.9、 すべて 9 月 1 日公開)、そしてローテーションすべき資格情報の一覧。手順五はその すべての先へ手を伸ばします。誰がやられたかを語れるサーバーが攻撃者のものだから です。Coder にはそれが見えず、そう明言しています。侵害されたすべてのデプロイを 確定的には特定できない、と。

だから問いはあなたに返ってきます。そしてそれは証拠についての問いです。あの モジュールを実行したマシン上の何かが coder-infra[.]com に接続しましたか。 本番の VPC なら、たいていのチームは一時間以内に答えられます。プラットフォーム チームの誰かがすでにフローログを持っているからです。

では同じ問いを、あなたのコーディングエージェントが働くマシンについて立ててみて ください。エージェントは terraform init を、npm install を、make を、 コンテナビルドを、テストスイートを、リポジトリの中で見つけたスクリプトを、 モジュールがダウンロードしたバイナリを実行します。そのどれもが接続を開き、 そして誰も、一週間後にあなたが検索できる場所にはそれを書き留めていません。 エージェント自身のトランスクリプトにはモデルが行ったツール呼び出しが残ります。 Terraform モジュールの中のシェルスクリプトがネットワークに対して何をしたかの行は ありません。モデルはそれが起きたところを一度も見ていないからです。

手順五の答えを、事が済んだ後から買うことはできません。要求がマシンを出る瞬間に 何かがそれを記録していたか、さもなければ検索するものが何も無いか、どちらかです。

そのスクリプトが見つけたはずのもの

ペイロードの言い分どおりに、走らせてみましょう。環境を舐めて資格情報を探し、 SSH 鍵を読み、OIDC トークンと外部認証トークンをつかみ、データベースの パスワードが入った設定変数を拾い、その一式を coder-infra[.]com へ送ります。

Bromure Agentic Coding は、各ワークスペースに Apple の ハイパーバイザ上の専用 Ubuntu VM を与え、ゲストからネットワークへ出る唯一の経路 としてホスト側プロキシを置きます。同じ探索をそこで走らせると、資格情報の一式が そろって返ってきます。すべて偽物です。

ANTHROPIC_API_KEY には sk-ant-api03-brm-… のおとりが入り、GH_TOKEN には 正しい長さの ghp_ 文字列が入り、~/.git-credentials~/.docker/config.json~/.kube/config には対応するプレースホルダが入ります。 形は保たれるので、ghdockerkubectl も文句を言わずに受け取ります。 プロキシは本物の値をあなたの Mac 上で暗号化したまま保持し、要求が VM を出た後、 その資格情報が発行された相手ホスト宛てのときにだけ、線上で差し替えます。これを 切るスイッチはありません。アプリにおける資格情報の仕組みそのものです。

SSH 鍵はいちばん明快な事例です。VM の中に読むべき鍵が存在しません。 SSH_AUTH_SOCK は仮想ソケット越しのブリッジでホスト上のエージェントを指し、その プロトコルには「このチャレンジに署名せよ」という要求はあっても「鍵を渡せ」という 要求はありません。ls ~/.ssh/id_* は空で返り、git push は動き続けます。

AWS には独立した段落がふさわしいでしょう。Terraform が相手にするのがそこだから です。VM の ~/.aws/config はヘルパーを指し、そのヘルパーはあなたの本物の アクセスキー ID と偽のシークレットキーを組にして返します。Terraform も AWS の SDK も boto3 も aws CLI も、設定なしでそのヘルパーを読み、偽の鍵で要求に署名し、 AWS なら拒否する体裁の整った署名を作ります。ホストはその署名を外し、外へ出る 途中で本物のシークレットで署名し直します。AWS の材料を求めて環境を漁った スクリプトが持ち帰るのは、キー ID と、何も認証しない 40 文字です。

そして戦利品をどこかへ送らねばならず、そこにワークスペースの外向き ファイアウォールが座っています。Unmatched traffic を Deny にすれば、VM は あなたが挙げたホストにだけ到達し、他には届きません。適用はゲストが手を出せない 二か所で二重に行われます。仮想ネットワークスイッチが宛先 IP と DNS から 読み取ったホスト名で全プロトコルの各フローを照合し、プロキシが TLS のサーバー名 でもう一度照合します。三日前のドメインをその一覧に入れた人はいません。ルールの 編集は再起動なしで実行中のセッションに届くので、ビルドが走っている最中に deny any coder-infra[.]com を足せます。

それでも通信が出て行ったとしたら、ワークスペースが既定の全許可の姿勢で動いて いたか、宛先が普通に見えたからです。そこでおとりが二つ目の仕事をします。 プロキシは外向きの要求すべてを、ヘッダも本文も走査し、発行先ではない場所へ 向かう偽の資格情報を探します。coder-infra[.]com 宛ての sk-ant-api03-brm-… 文字列に、曖昧さはありません。プロキシは一バイトも宛先に 届く前に HTTP 451 で遮り、VM を一時停止し、その資格情報と両方のホストを名指し した警告を上げます。Bromure はそのワークスペースを侵害済みと記録し、ディスクと ホームを消すまで二度と起動しません。

手順五の答えが、一週間後にログから再構成するのではなく、起きているその場で 手に入ります。

モジュールを実行したマシンでterraform apply → dlp.shビルド自身の権限で動く何を集めるかプロビジョナの環境変数と機密SSH 鍵、外部認証の資格情報ユーザーの OIDC トークンcoderd 設定 — DB のパスワード外向き: 一日中 HTTPS を投げるビルドホストからの HTTPS POST 一件「やられた?」→ 無いかもしれないログを検索本物の資格情報が出て行き、要求は誰も記録していない。Bromure Agentic Coding のワークスペースでterraform apply → dlp.shやはり動く — ubuntu として、ゲスト内で使い捨て VM · 唯一の出口はホスト側プロキシ環境変数も設定トークンもすべておとり秘密鍵のバイト列は無い — 署名はホストが vsock でAWS: 本物の key ID、偽の secret、ホストが再署名外向きは既定で拒否 — 折り返しは拒絶範囲外へ向かうおとり → HTTP 451、VM 一時停止、ワークスペースは侵害済みどのプロセスの要求にも 1 行が残る集めたのはおとり、プロキシは折り返しを拒み行を残した。
同じモジュール、同じ dlp.sh を二台のマシンで。左では本物の環境変数、本物の SSH 鍵、本物のトークンを見つけ、その折り返しは誰も書き留めていない普通の HTTPS 要求です。右ではおとりを見つけ、秘密鍵のバイト列は無く、署名用のシークレットは何も認証せず、折り返しは既定で拒否する外向きファイアウォールに当たり、範囲外のホスト宛てのおとりが VM を止めます。

アドバイザリに一つのコマンドで答える

ホスト側プロキシは、VM から出るすべての要求について、どのプロセスが出したかに 関わらずメタデータの記録を書きます。時刻、ホスト、ポート、メソッド、パス、 ステータス、遅延、そして資格情報の差し替え前に測ったバイト数。ワークスペースの Session traceActivity only にすれば、要求本文を一切保存せずにその 記録が得られます。あとは bromure-cli trace hostnames my-workspace が、そのワークスペースが接触した 別個のホストを件数つきですべて出力します。Coder の手順五が、コードの走った マシンについて、一行で。

エンジンが何を判断したか

Security Timeline(Window → Security Timeline…)は、Bromure のエンジンが 何を判断したかを見る別のビューです。外向きファイアウォールの判定、プロキシが 仲介した資格情報、サプライチェーンの遮断が、すべて色分けされ絞り込めます。 三日前に登録されたドメインへの拒否された接続は、ビルドがまだ走っている最中に 赤い Firewall の行として現れます。他人のアドバイザリにどこを見ろと言われた後で 一か月分のフローログを grep するのではなく。

ゲストがあなたの Mac に届く先は一つだけです。共有フォルダ、つまり本物の ファイルシステムへの生きた読み書きの窓で、侵害時の消去は設計上そこに手を出し ません。ホームディレクトリではなくプロジェクトディレクトリを共有すれば、id_rsa.env を狩るスクリプトが歩き回るのは、チェックアウトとパッケージキャッシュ だらけの Linux のホームです。

出所を確認できないなら、出口を見張る

出所の確認はすべて一つの動作に還元されます。届いたものを、すでに信頼している何か と比べる。名前、公開者、署名、ロックファイルの中のハッシュ。この動作は、攻撃者が 比較対象として何かを経路に置かねばならないあいだは効きます。経路の一区間を握った 攻撃者はそこに何も置かず、Coder のオリジンプールは経路の一区間でした。8 月の BdThemes 侵害も同じ形でした。 乗っ取られたストレージバケットが、35 万の WordPress サイトが取得する JSON フィードを書き換えた。リリースも、変更されたファイルも 無しに

14 時間は一営業日です。Coder は気づき、翌朝にパッチを出し、ハッシュと指標を公開 し、何を確認すべきかを読者に伝えました。それでもアドバイザリは、各読者に自分の 外向き通信を見に行くよう頼んで終わります。向こう側の記録はもう無いからです。

アドバイザリが挙げる鍵をローテーションし、キャッシュされたモジュールを消し、 パッチを当ててください。そのうえで、次の一件が何に手を届かせてよいかを決めて ください。次は、あなたが疑う理由を持たなかった経路からやって来るのですから。 Bromure Agentic Coding を導入し、ビルドに専用のマシンを与え、 アドバイザリが出たときに答えが用意されている状態にしてください。