ログインしているだけで十分だった
ディスクの空きを作っていた開発者が、隠しディレクトリに 700 MB を見つけて糸を引いた。Z.ai の ZCode が、作業ツリー全体を tar で固め、42,411 ファイルのうち 86.6% が .git というものを、Z.ai だけが持つ鍵で暗号化し、Alibaba Cloud のバケットへ直接投げていた。攻撃者もいない、CVE もない、マルウェアもない。引き金はログインしていることだった。クライアントがアーカイブを暗号化していたので、どのスキャナも中身を読めず、残った事実は宛先とサイズだけだった。それは、Bromure Agentic Coding がワークスペースの出すすべてのリクエストについて書き留めるフィールドのうちの二つであり、しかも VM の中のツールが選べない線の上でのことだ。
あなたのコーディングツールは、ソースコードをどこかへ送るために侵害されている必要 はない。デフォルト設定が一つあれば足りる。ベンダーを信頼するかどうかが問題では ない。あなたが気づけるのかを問うべきだ。
あなたは満杯のディスクの空きを作っている。ホームフォルダの隠しディレクトリが
700 メガバイト以上を占めていて、そんなサイズのものをインストールした覚えはない。
その中の checkpoints というフォルダの下に、拡張子が .enc の 313 MB のファイル
が一つだけあり、その隣のメタデータレコードには failureCount: 564 と書かれて
いる。
頼んだ覚えのないものに対する、五百六十四回の試行である。
七百メガバイトと、引くべき糸
ferstar として知られる中国の開発者がそこから始めたのが 9 月 18 日で、彼が見つけた もののリバースエンジニアリング報告 は全文読む価値がある。そのディレクトリは ZCode、GLM モデルを手がける企業 Z.ai のデスクトップ AI コーディングクライアントのものだった。CSO Online と InfoWorld はどちらも、彼による挙動の要約をそのまま載せている。
「ログインしているあいだは常に、ZCode はあなたのワークスペース全体を — 完全な .git 履歴、LFS アセットキャッシュ、reflog、グローバルなアプリ設定ごと — 黙って パッケージ化し、暗号化して、Aliyun OSS へ直接アップロードする。」
引き金は最初の節にある。あなたがした質問でもなく、あなたがオンにしたインデックス 機能でもない。有効なセッショントークン、キャプチャプロセスが待っているのはそれだけ だ。一つのセッションのログには 62 件のキャプチャイベントが記録されていた。彼が アーカイブを削除すると、クライアントは三十分以内に新しいものを作り、失敗カウンタを 一つ増やした。
箱の中に入っていたもの
彼が分解したスナップショットには 42,411 ファイル、暗号化前 345 MB、暗号化後 313 MB が入っていた。その 345 MB の内訳はこうだ。
.git/lfs/: 196.1 MB、56.8%.git/objects/: 102.2 MB、29.6%.git/logs/: 0.6 MB、0.2%- 残りすべて、つまり実際のソースとドキュメント: 46.2 MB、13.4%
アップロードの 86.6% は .git だった。 そのアーカイブは、リポジトリの履歴に
あるすべてのオブジェクト、LFS キャッシュが引いてきたすべての大きなバイナリ、そして
reflog を運んでいた。reflog は、あなたがいたすべてのブランチと、作ってから取り消し
たすべてのコミットを並べたものだ。あなたの作業ツリーを読む者は、あなたが何を出荷し
ているかを知る。あなたの .git を読む者は、あなたが何を試し、何を戻し、誰がコミッ
トしたかを知る。
どちらのスイッチも、向こう側を制御していた
ZCode には、注意深い開発者なら見つけてオフにしたはずの設定が二つ付いていた。一つ は体験最適化、もう一つはリポジトリスナップショットの索引付けだ。どちらも実際に何か をしていて、どちらも名前が示すことはしていなかった。
一つめは、Z.ai がそのデータを使う権限を持つかどうかを制御していた。二つめは、到着 したスナップショットをサーバが索引付けするかどうかを制御していた。梱包とアップロー ドはどちらにも関係なく走った。キャプチャのサイドカーは、トークンプロバイダから有効 な JWT を受け取った時点で起動し、その経路のどこにもユーザの設定は入っていないから だ。両方のスイッチをオフにしても、クライアントはあなたのリポジトリを tar で固め、 暗号化し、バケットへ投げ、そのあと何が起きるかについては控えめにすると約束した。
一つのベンダーのバグだが、この形はどこにでも繰り返される。自分が制限している プログラムの内側に住む制御は、お願いにすぎない。それは、あなたがそれを必要とする ためには壊れていなければならない、まさにそのコードの上で動いている。
線の上の何も、それを読めなかった
クライアントは各アーカイブをマシンから出る前に AES-256-CTR で暗号化し、その対称鍵
を zcode.z.ai から渡された RSA-OAEP-SHA256 の公開鍵で包んでいた。対応する秘密鍵
は降りてこない。ferstar は自分のアーカイブを開けなかった。開けられる唯一の当事者
は、バケットを持っている側だった。
これで、バイトを読むことで機能するあなたの防御はすべて外れる。シークレットスキャナ はその tarball の中に AWS の鍵を見つけないし、ディープパケットインスペクションが見 るのは、暗号文に包まれた暗号文を運ぶストレージエンドポイントへの TLS セッション だ。
生き残る事実は二つ。どこへ行ったかとどれだけ大きかったかである。あなたの チームがデプロイしていないリージョンのオブジェクトストレージホストへの 313 MB の POST は目立つ。ただし、あなたのツールがどこへ接続し、どれだけ送るかを誰かが書き 留めている場合に限る。誰も書き留めていなかった。だから、これを見つけるのにログを 一瞥する代わりに、ディスク容量の考古学と一週間のリバースエンジニアリングが必要に なった。
出口はどこにあるか
Bromure Agentic Coding は、エージェントとそのツールチェーンをあなたの Mac 上の Ubuntu VM の中に置き、その VM にインターネットへ届く道をちょうど一つだけ与える。 ホスト上、ゲストの外で動く man-in-the-middle プロキシで、VM の中のどのプロセスも 再設定できない場所にある。
これはソフトウェアに何も要求しない。仮想ネットワークスイッチが VM のポート 80
とポート 443 のフローをホストのプロキシへ透過的に迂回させる。必要な環境変数はなく、
ゲストが解除できるものもなく、すべてのワークスペースでデフォルトで有効だ。
HTTPS_PROXY を無視し、自分でソケットを開いてストレージエンドポイントへ直接話す
ツールも、やはりそこを通る。傍受はゲストの中ではなく、ゲストが差し込まれている
スイッチの中に住んでいるからだ。
誰も頼んでいない 313 MB の POST には、それから四つのことが起きる。
すべてのリクエストが記録を残す
プロキシを通るすべてのリクエストはトレースレコードを残す。タイムスタンプ、 ホスト、ポート、メソッド、パス、ステータス、レイテンシ、そして 資格情報の 差し替え前に測ったリクエストのバイト数だ。デプロイしたことのないホストへの リポジトリ大のアップロードに気づくのに、アーカイブを復号する必要はない。 サイズと宛先は列だからだ。一方で並べ替え、もう一方で絞り込めばいい。
Mac から出ていかない
ワークスペース単位の外向きファイアウォールは、順序付きのルール表(アクション、
プロトコル、ホストまたは CIDR、ポート、HTTP メソッド)で、末尾は
default allow か default deny になる。許可リスト方式でワークスペースを
動かせば、そのアップロードには行き場がない。ルールを施行する場所は二つある。
スイッチが宛先 IP と DNS から覗き見たホスト名を照合し、プロキシが TLS の
サーバ名をもう一度照合する。あなたの変更は動いているセッションに即座に届く。
すべての判定が一行になる
ファイアウォールの判定はすべて Security Timeline に着地するので、たとえば 564 回のように再試行を続けるツールは、静かに埋まっていくディスクではなく、 名前のついたホストに対する繰り返しの拒否として現れる。再構成に一週間かかった そのパターンこそ、あの画面が映すために作られているものだ。
詰め込まれる設定はおとりだ
ferstar の一文で最も気にすべき表現は「グローバルなアプリ設定」だ。Bromure の
ワークスペースの中には ~/.git-credentials、~/.docker/config.json、
~/.kube/config、~/.aws/config が存在して機能し、その中のすべての値は
構造を保った偽物である。VM の中のファイル、環境変数、プロセスのどれも、本物の
API キー、OAuth トークン、AWS シークレット、SSH 秘密鍵を持つことはない。
残りは範囲が片付ける。ワークスペースは一つのプロジェクト、あるいは一つの資格情報の
境界だ。二つのワークスペースが VM を共有することはなく、ゲストのホームはそれ自身の
イメージファイルである。ツールがワークスペースの中で tar に固めるものは、その
ワークスペースだけだ。つまり、あなたの Mac にある残り四十のリポジトリ、ホストの
~/.aws/config、あなたの SSH 鍵は入らない。鍵はそもそもゲストに届かない。署名は
vsock ブリッジ越しにホスト上で行われ、秘密のバイト列はそこに留まる。
そのあと Z.ai がしたこと
Z.ai は ferstar が公開した当日に謝罪した。同社は ZCode v3.14.0 でアップロード のワークフローを無効化し、InfoWorld によればこのバージョンは 9 月 19 日に出て、 リポジトリアップロードの異常に関する修正が挙げられていた。さらに結果を監査させる ためにセキュリティ企業 NSFOCUS を入れた。NSFOCUS は「zcode-prod という Alibaba Cloud OSS バケット内のすべてのデータオブジェクト、およびバケット自体が削除された」 ことを確認し、Z.ai は監査後に「ローカルのリポジトリスナップショットの生成、または ローカルファイルの外部送信を引き起こしうる機能的な経路は特定されなかった」と述べ、 そのコードは「モデルの学習に使われたことは一度もない」としている。そのうえで一式を オープンソースにした。クライアント、バックエンド、共有 UI、Agent CLI、ランタイムを Apache-2.0 で、GitHub 上に。これは業界の水準を 上回るし、次に確かめたい人は逆アセンブラを必要としないということでもある。
CSO の記事で引用された Semgrep の Cris Thomas は、この分類に名前を与えた。「これは 本当のところ AI モデルの問題ではなく、昔ながらのセキュリティアーキテクチャの問題 だ。」彼が求めているのは、何がマシンを出ていくのか、どこへ行くのか、どれだけの期間 保持されるのかをベンダーが開示すること、しかも「最大ではなく最小の権限をデフォルト で有効にして」だ。顧客がそれを外から強制することはできない。
デフォルトこそが脅威モデルである
ここでは誰も誰かを攻撃していない。資格情報を盗んだ者も、パッケージを汚染した者も、 モデルを言葉で丸め込んだ者もいない。製品が、作者たちが作ったとおりのことをした。 そして彼らが作ったのは、あなたのリポジトリを、あなたが読めないどこかへ送り出すこと だった。この事件はデフォルトであり、デフォルトを選ぶのは、利害があなたのすぐ隣を 走っている人たちだ。
だからこそ、境界をツールの外に置くべきだという議論になる。コーディングアシスタント が敵対的だと信じる必要はない。見えないものは監査できないということ、そしてベンダー の設定画面はそのスイッチを置く場所としては妙だということに気づくだけでいい。 エージェントとそのツールチェーンは、彼らが開くすべての接続があなたの所有する線を 横切る場所で、その横断の記録があなたの Mac の上に書かれる場所で、313 MB の アップロードを止めるルールが、誰かが尊重してくれたチェックボックスではなく、あなた が打ち込んだ一行である場所で動かすべきだ。
そうすれば「私のコーディングツールは何を送っているのか」は、火曜の午後に開く一枚の 画面になる。研究プロジェクトではなくなる。