トークンは同じヒープの中にあった
Accomplish の Oren Yomtov は、OpenAI の Codex サンドボックスから二度脱出した。一方の脱出は最も厳しいモードである read-only から始まった。信頼されていない JavaScript が v8.getHeapSnapshot() を呼び、両者が共有するヒープの中から信頼コンテキストの認可トークンを読み取ったのだ。もう一方は apply_patch に自分自身の書き込み権限をファイルシステムのルートまで広げさせ、そのうえで .zshrc に一行を追記した。OpenAI は両方を八日で修正した。だが、それらを生んだ性質には修正パッチがない。番人と、番人が守るコードとが、ひとつのプロセス内の二つの変数だったのである。Bromure Agentic Coding は、番人をあなたの Mac で、エージェントを VM の中で動かす。
非信頼コンテキストはサンドボックスを破ってなどいない。Node にヒープの スナップショットを求め、サンドボックス自身の認可トークンがただの文字列として そこに転がっているのを見つけ、それを使っただけだ。
誰かから送られてきたリポジトリに Codex を向ける。あなたは慎重なので、
read-only で走らせる。エージェントが見ることはできても触れられないモードだ。
エージェントはコードを読む。そのコードの一部はペイロードであり、数秒後には
あなたの Mac の上で、サンドボックスの外で、画面に何の確認も出ないまま、
コマンドが動いている。
これが Heapjack、 Accomplish の Oren Yomtov が今月公開した 二つの Codex サンドボックス脱出のうちの一つだ。 BleepingComputer は 9 月 20 日に報じている。 Yomtov は 8 月 12 日に両方を OpenAI に報告し、OpenAI は八日以内に修正した。 Heapjack は Codex Desktop ビルド 26.818.21641 で、もう一方は Codex CLI 0.149.0 で。OpenAI の声明は短いほうの部類だ。「研究者の皆様がご連絡くださり、 発見を共有してくださったことに感謝します。両方の問題には 8 月に対処しましたが、 私たちはサンドボックスを継続的に強化し続けています」
最初のバグがなぜ存在したのか。そこがパッチより長く残る部分である。
ひとつのプロセス、ひとつのヒープ、ひとつのトークン
Codex Desktop には JavaScript を実行する node_repl というツールがある。その
内部に二つのコンテキストを抱えている。親プロセスに実際の処理を依頼することを
許された信頼コンテキストと、エージェントが評価しようとしているものを何であれ
実行する非信頼コンテキストだ。この二つの隔たりが、サンドボックスである。
両方のコンテキストは同じ Node プロセスの中に生きている。Yomtov の言葉で言えば、 「ひとつの Node プロセス、ひとつの V8 アイソレート、両方のコンテキストが共有する ひとつのヒープ。トークンはそのヒープの中の文字列だ」
トークンは、親プロセスが二つを見分けるための手立てである。正しいトークンを 添えてパイプに届いた要求は信頼され、実行される。添えていない要求はされない。 中身はランダムな UUID で、隠す相手のコードがあなたのメモリを読めないかぎり、 秘密としては申し分ない。
読めるのだ。v8.getHeapSnapshot() は Node の公開 API であり、プロファイリング用の
道具であり、ヒープ上のあらゆるオブジェクトを、文字列も含めて、そっくり吐き出す。
非信頼コンテキストはこれを呼び、UUID の形をしたものだけを絞り込み、
ひと握りの候補を手にする。本物を選り分けるにはもう一手いる。ひとつずつ親に
試し、返ってくるものを見るのだ。誤ったトークンには「権限がありません」と返る。
正しいトークンには、要求の残りの部分についての苦情が返る。Yomtov は、
二つめの返答を生んだ文字列を手元に残す。
トークンを手にした非信頼コンテキストは、信頼側が使っているのと同じ stdout
パイプに要求を書き込む。親はトークンを照合し、有効だと判断し、サンドボックスの
外でコマンドを実行する。そこから先は、open 経由のシステムコマンド、Docker
ソケット、設定ファイルの書き換えである。
これらすべてが read-only からだ。もう一度 Yomtov を引く。「そのすべてが
read-only で動いた。最も厳しいサンドボックスモード、エージェントは何も
書けないはずの場所で」
育ってしまう書き込み権限
二つめの脱出である Overpatch のほうは、もっと素朴だ。Codex CLI にはファイルを
編集する apply_patch というツールがあり、渡されたパッチに書かれたパスから、
どれだけの書き込み権限が要るのかを割り出す。そのパスはモデルから来る。そして
モデルは、いまリポジトリを読んでいる最中である。
そこで、パッチのある項目が /tmp を指定する。中身は何も起こさない変更だ。変更は
失敗する。だが、それが暗に求めた権限の付与のほうは失敗しない。書き込み権限は
ファイルシステムのルートまで広がる。同じパッチの二つめの項目が、そこでシンボリック
リンク越しに $HOME へ書き込み、.zshrc に一行を追記する。
まだ何も脱出してはいない。脱出が起きるのはもっとあと、あなたが端末を開き、
シェルがその一行をあなたとして実行する瞬間だ。その経路のどこにも
サンドボックスはない。こちらは日常のモードである workspace-write で動いた。
異なる二つのバグ、異なる二つの製品。そして、その両方を覆う一文が記事にある。 「強制する側のものが、強制される側のものの内側に座っていた」
番人と守られる側が、ひとつのアドレス空間に
抑え込む対象のコードと同じプロセスの中に生きているサンドボックスは、 ひとつの取り決めにすぎない。誰かが本気で覗き込むまでは持ちこたえる。トークンは ヒープの中にある。権限の計算は攻撃者由来のパスを読む。パイプは同じパイプだ。 どれもが直せるものであり、OpenAI はどれも八日で直した。この種の次のバグは、 同じヒープの中の別の文字列だろう。
Yomtov 自身の結論はアーキテクチャの話である。「だからこそ Accomplish は エージェント全体を VM の中で動かしている……ゲストの中にあるものはすべて、 root も含めて信頼しない。何が許されるかを決めるものは、何ひとつその中で 一緒に動いていないからだ」
それが Bromure Agentic Coding の出荷している設計だ。エージェントは、macOS 自身が備えるハイパーバイザーである Apple の Virtualization.framework のもと、 Ubuntu 24.04 のゲストの中で動く。ゲストは構造上、root も含めて信頼されない。 何が許されるかを決めるコードは、vsock の向こう側でホストのプロセスとして動く。 ゲストの内側にあるどのトークンも何ひとつ認可しない。認可するに値するものは、 すべてゲストの外にあるからだ。
それぞれの脱出が代わりに出会うもの
Overpatch のほうが具体的なので、そこから始めよう。この手口は $HOME の
.zshrc に追記して終わる。Bromure のワークスペースでは、ゲストの $HOME は
home.img、その VM に接続された ext4 イメージである。あなたの Mac のホーム
ディレクトリはその中にない。ゲストがあなたの Mac 上で届く先は、あなたが共有した
プロジェクトフォルダだけだ。最大八つ、あなたが選び、ワークスペースエディタに
並ぶもの。あなたが端末を開いたときにシェルが読むドットファイルは、自分でそこに
置かないかぎり、その中には入っていない。
Heapjack のほうが難しい。Bromure の中に、ゲスト内部のヒープスナップショットの 手口を見抜くものは何もないし、見抜くつもりもない。変わるのは、脱出の到着先だ。 Codex では、脱出したコードはあなたの SSH 鍵とクラウド資格情報を握っている アカウントに着地した。Bromure のワークスペースでは、次のような Linux VM に 着地する。
資格情報はダミーである
sk-ant-api03-brm-…、ghp_ の形をした文字列、シークレットが無効な ~/.aws
プロファイル。構造は保たれているのでツールは受け付けるが、上流では何の価値も
ない。本物の値はホスト側のプロキシのメモリに置かれ、外へ出る途中で差し替え
られる。差し替え先は、その値が属するホストか、そのサブドメインに限られる。
持ち出そうとすれば鳴子に触れる
プロキシは、外向きのすべてのリクエストのヘッダーとボディを、そのワークスペース 自身のダミー目当てに走査する。鋳造された先とは別のところへ向かうダミーは HTTP 451 で遮られ、一バイトも転送されない。VM は一時停止し、赤い 資格情報の仲介 の行がセキュリティタイムラインに現れる。
ネットワークはあなたが書いた一覧である
送信は default allow か default deny で終わる順序つきのルールセットだ。
仮想スイッチは VM のポート 80 と 443 の通信をホストのプロキシへ迂回させる。
環境変数は一切介在せず、ゲストが解除できるものも何もない。編集は動作中の
セッションに即座に効く。
push は Mac の上で尋ねられる問いである
Git フォージの資格情報は書き込みポリシーを帯びており、新しいワークスペースの
既定は 書き込み前に確認 だ。git push は書き込みである。ダイアログは
その操作そのものを名指しし、あなたのデスクトップに描画される。VM の中の
攻撃者は、それをクリックできない。
それでも事が起きたときは、メニュー項目ひとつで復旧する。侵害時のフローは ワークスペースのディスクとホームを消し、あなたの設定とトークンと SSH 鍵は残す。 本物の資格情報はあなたの Mac から一度も出ていないので、ローテーションするものは 何もない。ディスクをリセット はシステムイメージを再クローンする。Bromure の どの操作も、共有されたプロジェクトフォルダには触れない。確認画面にもそう書いてある。
最も厳しいモードが、負けたほうだった
この話に出てくる開発者は、何ひとつ間違っていない。リポジトリが自分のものでは
なかったから、わざわざ、そのツールが用意する最も厳しい設定を選んだのだ。
read-only は本物の機能であり、本物の仕事をしていた。共有されたヒープの中の
文字列ひとつが、それをただの提案に変えてしまうまでは。
モードも、許可リストも、承認のプロンプトも、持っている価値はある。そしてその どれもが、それを強制しているコードが、制約する相手のコードの手の届かない場所に 座っていることを前提にしている。それはメモリ配置についての主張であり、 確かめに行けるものだ。Yomtov は Codex を二度、二つの製品にまたがって確かめ、 どちらも答えは「いいえ」だった。
だから、その答えがもはや問題でなくなる場所でエージェントを動かせばいい。root を与え、好きなものを入れさせ、誰かから送られてきたリポジトリを読ませ、そして あらゆる資格情報、あらゆる宛先の判断、あらゆる push の確認を、ハイパーバイザーの 向こう側に置いておく。次の脱出が到着するのは、あなたがもとより捨てる気で いたマシンの中になる。