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

ログインしているだけで十分だった

ディスクの空きを作っていた開発者が、隠しディレクトリに 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 を読む者は、あなたが何を試し、何を戻し、誰がコミッ トしたかを知る。

ログイントークン一つ、四つの手順、攻撃者はいないあなたのマシン、ログイン中作業ディレクトリ.git/lfs, .git/objects, .git/logsソース、docs、グローバル設定42,411 ファイル · 345 MB設定の 2 つのスイッチ体験最適化 → 学習利用スナップショット索引 → サーバ側索引どちらも梱包を止めない1 · zcode.z.aiOSS のフォーム署名を返すそして RSA 公開鍵も秘密側はサーバに残る2 · ローカルで梱包・封印tar.gz → AES-256-CTR鍵 ← RSA-OAEP-SHA256313 MB、あなたには読めない3 · Alibaba Cloud OSS へ POSTzcode-prod バケット直接 — Z.ai のサーバは通らないキャプチャ 1 件ごとに 1 オブジェクト4 · OSS コールバック到着をバックエンドに伝えるスナップショットは登録され向こう側で索引可能になる何が引き金だったか有効なログイントークン。プロンプトでも、質問でも、インデックス機能でもない。一つのセッションで 62 件のキャプチャイベント。アーカイブを削除すれば三十分で作り直される。failureCount: 564
アーカイブがマシンを出ていった経路。ZCode クライアントは zcode.z.ai にアップロード用の資格情報を求め、OSS のフォーム署名と RSA 公開鍵を受け取った。そして作業ディレクトリを tar で固め、その公開鍵で包んだ鍵を使ってアーカイブをローカルで AES-256-CTR で暗号化し、結果を Alibaba Cloud のオブジェクトストレージへ直接投げた。Z.ai 自身のアプリケーションサーバは経由せず、OSS のコールバックがバックエンドにオブジェクトの到着を伝えた。対応する秘密鍵はクライアントに届かないため、ユーザは自分のアーカイブを復号できない。引き金は有効なログイントークンであり、アシスタントを使ったかどうかではない。あるセッションでは 62 件のキャプチャイベントが記録され、ファイルを削除すると三十分ほどで再梱包が走った。

どちらのスイッチも、向こう側を制御していた

ZCode には、注意深い開発者なら見つけてオフにしたはずの設定が二つ付いていた。一つ は体験最適化、もう一つはリポジトリスナップショットの索引付けだ。どちらも実際に何か をしていて、どちらも名前が示すことはしていなかった。

一つめは、Z.ai がそのデータを使う権限を持つかどうかを制御していた。二つめは、到着 したスナップショットをサーバが索引付けするかどうかを制御していた。梱包とアップロー ドはどちらにも関係なく走った。キャプチャのサイドカーは、トークンプロバイダから有効 な JWT を受け取った時点で起動し、その経路のどこにもユーザの設定は入っていないから だ。両方のスイッチをオフにしても、クライアントはあなたのリポジトリを tar で固め、 暗号化し、バケットへ投げ、そのあと何が起きるかについては控えめにすると約束した。

一つのベンダーのバグだが、この形はどこにでも繰り返される。自分が制限している プログラムの内側に住む制御は、お願いにすぎない。それは、あなたがそれを必要とする ためには壊れていなければならない、まさにそのコードの上で動いている。

スナップショット一つ: 42,411 ファイル、暗号化前 345 MB.git/lfs — 196.1 MB.git/objects — 102.2 MBソース 46.2 MB56.8%29.6%13.4%マシンを出たものの 86.6% はリポジトリの履歴 — オブジェクト、LFS キャッシュ、reflogreflog だけでも、あなたがいたすべてのブランチと、作って取り消したすべてのコミットの一覧だ。グローバルなアプリ設定ファイルも一緒に運ばれた。
345 MB のうち、コードはたった 46 MB だった。ferstar が復元したスナップショットには 42,411 ファイルが入っていた。Git LFS キャッシュが 196.1 MB、Git オブジェクトが 102.2 MB、reflog が 0.6 MB で、これがペイロードの 86.6% を占め、実際のソースとドキュメントは 46.2 MB だった。作業ディレクトリをアップロードするツールは、あなたがそのリポジトリで書いたすべてを、捨てたブランチごとアップロードする。

線の上の何も、それを読めなかった

クライアントは各アーカイブをマシンから出る前に 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 ブリッジ越しにホスト上で行われ、秘密のバイト列はそこに留まる。

Mac 上のツールホームディレクトリ~/work/* : 全リポジトリ、全 .git~/.aws/config, ~/.docker, ~/.ssh : 本物ツールが何を読み、どこへ送るかを選ぶ。ツールが自分の設定を自分で適用する。313 MB、暗号化済み、ストレージバケットへ宛先の記録なし。サイズの記録なし。どうやって気づくかディスクが足りなくなり、リバースエンジニアリングを始める。ワークスペースの中のツールワークスペース VM — Ubuntu ゲスト、1 プロジェクト/home/ubuntu — このリポジトリ、この作業~/.aws/config, ~/.docker — 偽の値ツールは今も何を読み何を送るかを選ぶ。経路は選べない。:80 と :443 をホストのプロキシへ迂回ホスト · メソッド · パス · バイト数 → tracedeny web *.aliyuncs.com · default denyワイヤで拒否し、Security Timeline に記録。
同じツール、二つの置き場所。Mac の上で直接動かせば、それはあなたが持つすべてのリポジトリ、ホームディレクトリのすべての資格情報ファイル、そして仲立ちのないインターネットへの経路の隣に座る。アップロードはほかと変わらない TLS セッションで、アーカイブは暗号化されているので、内容を読む仕組みは何も助けにならない。Bromure のワークスペースの中で動かせば、ポート 80 とポート 443 のフローは、そのツールが再設定できないホスト側のプロキシへ迂回させられる。プロキシは宛先とバイト数を記録し、外向きのルールがそのリクエストを出すかどうかを決め、そのツールが梱包した設定ファイルに入っているのは、本物の値が一度も VM に入っていないプレースホルダである。

そのあと 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 の アップロードを止めるルールが、誰かが尊重してくれたチェックボックスではなく、あなた が打ち込んだ一行である場所で動かすべきだ。

そうすれば「私のコーディングツールは何を送っているのか」は、火曜の午後に開く一枚の 画面になる。研究プロジェクトではなくなる。