Skip to content

隔離して走らせる

回数の上限はここまでで測ったが、範囲の上限がまだ無い。権限の確認は道具を呼ぶ前にモデルの出した文字列を見て決めるもので、隔離は呼んだあとに OS が強制するものだ。別物なので、隔離を上げると承認のほうは消える。実物はそう作られている。ただし鍵と出口とプロンプトインジェクションは、境界の内側に置いても効かない。

この章で読むもの

この章もコードを書かない。エージェントの枠組みで作った枠組みには、止め方が1種類しか入っていなかった。回数の上限だ。

枠組みの形が2つ出てくるので、先に置いておく。ループグラフも、止まるまで繰り返す点は同じだ。違うのは回る道になる。ループは1手ごとにモデルへ「次はどうする?」と訊いて順番もモデルが決め、グラフは順番を先に書いておいて、書けないところだけ訊く。グラフの箱1つをと呼ぶ。

上限は、ループには手数、グラフには同じ節へ入れる回数として置いた。Hermes と Pi で読み替えるで見た実物も同じで、反復50、--max-turns、並列3、どれも回数を数えている。

回数で止めるのは、終わらないことへの手当てだ。やりすぎることへの手当てにはならない。1手で消せるものは1手で消える。

この章で読むのは、そちら側の道具になる。

                  何を止めるか              効かない相手
  -----------------------------------------------------------------
  回数の上限        実行が終わらないこと        1 手で消える
  (--max-turns)                             1 手で持ち出される

  範囲の上限        触れる先・出られる先        延々と回り続ける
  (隔離)                                     許した範囲の中で壊す
  -----------------------------------------------------------------

  どちらも要る。埋める穴が違う
回数の上限は実行が長引くのを止める。範囲の上限は1手が届く先を狭める。効く相手が違うので、片方だけでは埋まらない

順に見ていく。

  1. 確認と隔離は、効くところが違う: 前者は呼ぶ前の判断、後者は呼んだあとの強制になる
  2. 何を境界の内側に入れたかで、強さが決まる: Bash だけ隔離しても、隣で素通りする
  3. 隔離を上げると、承認は消える: 足し算ではなく、置き換えの関係にある
  4. 境界の内側に置いても効かないものがある: 鍵と、出口と、読ませる文章

① 確認と隔離は、効くところが違う

「安全に走らせる」で最初に出てくるのは、危ないことをする前に訊く、という形だ。実際、Claude Code も Pi も Hermes も持っている。

だがこれには弱点がある。訊くかどうかを決めるのは、モデルが出した文字列を見た判断だからだ。rm -rf / なら分かるが、名前から想像がつかないことをするコマンドは通る。スクリプトを1本呼ぶだけの行が、中で何をしても、外から見えるのは1行になる。

隔離はそこが違う。Claude Code のドキュメントが、この2つを並べて書いている。

Claude Code は、コマンドが走る前に、コマンドの文字列と、自動モードでは別の分類器がそれを安全と見なすかどうかに基づいて、権限の判断を下す。サンドボックスの境界のほうは、走っているプロセスに対して OS が強制する。だからモデルが何を選んだかに関わらず、そして許可されたコマンドが名前から想像される以上のことをしたとしても、境界は保たれる

言い換えると、確認は呼ぶ前に効き、隔離は呼んだあとに効く。

いつ効くか誰が決めるか破れ方
権限の確認道具を呼ぶ前文字列を見た判断名前から中身が読めないとき
隔離呼んだあと、ずっとOS境界の引き方を間違えたとき

枠組みの章の言い方をすると、確認は1パスの中の話で、隔離は実行全体に張ってある。だから確認をすり抜けたものも、隔離の内側には留まる。逆に、隔離しただけでは何を実行するかは制限されない。2つは重なるものではなく、直交している。

② 何を境界の内側に入れたかで、強さが決まる

隔離といっても、何を内側に入れるかで別物になる。Claude Code の組み込みサンドボックスは、Bash のコマンドとその子プロセスだけを包む。同じセッションで動くほかのものは、包まれていない。

組み込みサンドボックスの内側か
Bash のコマンドと子プロセス内側
Read / Edit / Write外側(権限規則で見る)
MCP サーバ外側。ホストでそのまま走る
フック外側。ホストでそのまま走る

だからドキュメントは、確認を全部飛ばす旗を使うなら組み込みサンドボックスでは足りない、と明記している。プロセス全体を包む必要がある。

--dangerously-skip-permissions を渡すと、Claude は先に訊かずに動く。…間違いを捕まえるための確認が無いので、選んだ隔離の境界が、あなたのシステムを守る唯一のものになる。--dangerously-skip-permissions のセッションは、必ずコンテナか VM か sandbox runtime の中で走らせること。そうすればファイル操作の道具も MCP サーバもフックも、境界の内側に入る。

包む範囲で並べると、こうなる。

包むもの中身Docker が要るか
Bash とその子プロセス組み込みサンドボックス(macOS は Seatbelt、Linux は bubblewrap)不要
プロセス全体sandbox runtime。同じ仕組みで claude ごと包む不要
開発環境ぜんぶdev container / 自前のコンテナ要る
OS ぜんぶVM、マイクロ VM不要(別の重さがある)
OS ぜんぶ(他人の機械)提供者が用意した隔離環境不要

Pi はここに違う答えを出している。サンドボックスを持たないと明言していて、理由まで書いてある。

これは意図的だ。…部分的な in-process サンドボックスは、セキュリティ境界だと誤解されやすいわりに、結局ホストのシェル、ファイルシステム、パッケージ管理、資格情報、そして拡張のコードに依存し続ける。本物の隔離は、OS か仮想化/コンテナの境界から来る必要がある。

代わりに、外側で包む形を並べている。プロセスごとコンテナに入れる、ホストで動かしたまま道具の実行だけをマイクロ VM に流す、方針を持つサンドボックスに預ける。Hermes と Pi で読み替えるの②で見た「道具を4つに絞る」のと同じ姿勢で、足りないものは外に出すという作りになっている。

「隔離」という語が2つある

ここで用語をひとつ切り分けておきたい。依存関係の隔離と、権限の隔離は別のものだ。

Nix を使った開発環境の道具(Devbox など)は「隔離された環境」と説明されるが、隔離しているのはどのパッケージのどの版が見えるかであって、そこで走るプロセスの権限ではない。実行しているのは自分のユーザで、ホームディレクトリも鍵も普通に読める。境界にはならない。

必要なら、そこからコンテナ定義を出力して、コンテナのほうを境界にするという組み方になる。前者は再現性の道具で、後者が安全の道具だ。名前が似ているだけで、埋める穴が違う。

③ 隔離を上げると、承認は消える

隔離と確認は直交している、と①で書いた。だが実物では、両方を積むのではなく、置き換えている

Hermes は端末の実行先を選べるようになっていて、localsshdockersingularitymodaldaytonavercel_sandbox がある。そして、こう書かれている。

dockersingularitymodaldaytonavercel_sandbox のバックエンドで走っているときは、コンテナ自体がセキュリティ境界なので、危険なコマンドの確認は飛ばされる

localssh だけが確認を残す。届く先がホストだからだ。

Claude Code のサンドボックスも同じ形をしている。自動許可モードは「境界が受け止めるから、いちいち訊かない」という設計で、訊く回数を減らすのが目的だと明記されている。境界を引いたぶんだけ、確認が要らなくなる。

置き換えない作りもある

ただし、これは唯一の形ではない。Codex は2つを別々の設定として持っている

設定
sandbox_moderead-only / workspace-write / danger-full-access(ほかに自作の権限プロファイル)
approval_policyuntrusted / on-request / never

どこまで触れるかと、いつ訊くかが独立している。①で「2つは直交している」と書いたが、Codex はそれをそのまま設定の形にしている。既定の組み合わせは workspace-write + on-request、つまり作業場所の中は自由に書けて、外へ出るときとネットワークが要るときだけ訊く

そしてもう1つ効くのが、既定でサンドボックスが入っていることだ。Claude Code は /sandbox で入れる形、Pi はそもそも持たない形だった。Codex は何も設定しなくても中にいる。

既定の隔離ネットワーク
Codexworkspace-write。最初から中にいる既定で切れている
Claude Code無し(/sandbox で入れる)入れれば許可制
Pi無し(外側で包む前提)素通し

外向きの通信が既定で切れているのは、④で見る持ち出しの話に直接効く。出られないなら持ち出せない。要るときは [sandbox_workspace_write]network_access を立てる、という順になっている。

3つを並べると、隔離の入れ方に3通りの立場があると分かる。製品が最初から入れておく(Codex)、使う側が入れる(Claude Code)、外側で包ませる(Pi)。②で見た道具の数と同じで、どれかが正しいのではなく、誰が決めるかが違う。

この置き換えは、片側だけ緩めると崩れる。コンテナに入れたつもりで作業ディレクトリの外まで書けるようにした、ホームディレクトリをそのまま渡した、というときに、確認のほうは既に消えている。ドキュメントも同じことを警告している。

効果のある隔離には、ファイルシステムとネットワークの両方が要る。ネットワークの隔離が無ければ、乗っ取られたエージェントは SSH の鍵のような機微なファイルを持ち出せる。ファイルシステムの隔離が無ければ、乗っ取られたエージェントはシステムに裏口を作ってネットワークに出られる。既定を広げるときは、片側の緩和がもう片側の制限を打ち消していないか確かめること

Docker で固める場合の既定も、そこまで踏まえて作られている。Hermes は Linux の権限をすべて落としてから必要なものだけ戻し、権限の昇格を禁じ、プロセス数に上限を置き、一時領域を実行不可で別に持つ、という形にしている。

④ 境界の内側に置いても効かないものがある

隔離は万能ではない。境界を越えて渡したものは、境界の中でも同じ力を持つ

。コンテナに環境変数として渡した資格情報は、コンテナの中でも外と同じことができる。広い権限のクラウドの鍵を渡したなら、隔離しても意味が無い。Hermes のドキュメントも、コンテナへ引き渡す環境変数は「端末コマンドのために意図的に注入されている」ので、悪意あるコードが持ち出せると書いている。Claude Code 側には、鍵のファイルを読めなくする設定と、環境変数を見せかけの値に差し替えて、許可した宛先に出るときだけ本物に戻す仕組みがある。ただし後者は、代理サーバが中身を見られる形にしないと成立しない。

出口。出られるなら持ち出せる。ドメインを許可制にする形はあるが、既定の代理サーバは TLS を終端しないので、中身は見ていない。だからクライアントが申告したホスト名で判断していて、そこを偽る手が知られている。ドキュメント自身が、github.com のような広いドメインを許すと持ち出しの経路になりうる、と警告している。終端させる設定は後から入ったが、試験的な扱いで、中身を絞り込むためのものではないと同じ節に書かれている。

読ませる文章。これがいちばん厄介で、Pi が正面から書いている。

プロジェクトのファイル、コメント、ドキュメント、コンテキストファイル、ビルド出力からのプロンプトインジェクションは、ローカルで動くエージェントに想定される危険であり、Pi が確実に防ぐことはできない。

Hermes もコンテキストファイルを走査する仕組みを持っているが、疑わしいものに印を付けるだけで、あらゆる場合に防げるとは言っていない。隔離は起きたことの被害範囲を狭めるもので、起きること自体は止めない。

そしてもう1つ。隔離してもモデルに送られるものは変わらない。読んだファイルはそのまま送られる。境界は手元の環境の話であって、送り先の話ではない。

設計の観点

  • 回数と範囲は別の上限: どちらか片方では埋まらない。長引くのと、やりすぎるのは違う失敗だ
  • 実行前の判断に頼りすぎない: 名前から中身が読めないものは通る。通ったあとに効くものが要る
  • 何を内側に入れたか数える: Bash だけ包んでも、隣でフックと MCP が素通りする
  • 確認を全部消すなら、境界を先に引く: 消したあとに残るのは境界だけになる
  • 隔離と承認は置き換えの関係: 境界を引いたから訊かない、という作りになっている。片側を緩めるともう片側が既に無い
  • ただし置き換えない作りもある: 触れる範囲と訊く条件を別々の設定として持てば、両方を独立に決められる
  • 既定で入っているかを見る: 製品が入れておくのか、使う側が入れるのか、外側で包むのか。誰が決めるかが違う
  • 両側を同時に見る: ファイルの範囲を広げたら出口も見る。出口を広げたらファイルの範囲も見る
  • 渡した資格情報は境界を越える: 中に入れた資格情報は、中でも同じ力を持つ。短命のものを最小限で渡す
  • 依存の隔離を安全の隔離と数えない: 再現性の道具は、境界ではない
  • 読ませる文章は止められない: 隔離は被害の範囲を狭めるだけで、仕込まれること自体は防がない

対照と実例

包む範囲Bashファイル操作MCP・フック出口の制御用意する手間
何もしない無し
Codex(既定)内側作業場所の中確認していない既定で切る無し(最初から)
組み込みサンドボックス内側権限規則外側ドメイン許可制小(macOS は素で動く)
プロセスごと包む内側内側内側ドメイン許可制
dev container内側内側内側firewall で既定拒否
自前のコンテナ内側内側内側自分で決める中〜大
VM・マイクロ VM内側内側内側自分で決める
提供者の隔離環境内側内側内側既定で許可制無し

裏どり:

  • 確認と隔離の違い(一次資料): 「Claude Code は、コマンドが走る前に…権限の判断を下す。サンドボックスの境界のほうは、走っているプロセスに対して OS が強制する。だからモデルが何を選んだかに関わらず、そして許可されたコマンドが名前から想像される以上のことをしたとしても、境界は保たれる」
  • 組み込みサンドボックスの範囲: Bash とその子プロセスのみ。「MCP サーバとフックは別のプロセスで、ホスト上で制約なく走る」。Read / Edit / WebFetch は権限規則で見る。実装は macOS が Seatbelt、Linux と WSL2 が bubblewrap で、Windows は非対応(WSL2 の中で動かす)
  • 確認を消すときの条件: 「--dangerously-skip-permissions のセッションは、必ずコンテナか VM か sandbox runtime の中で走らせること」。この旗は Linux と macOS で root や sudo からは弾かれる。既知のサンドボックスの中でだけ、その検査が飛ばされる
  • Hermes のバックエンド: local / ssh / docker / singularity / modal / daytona / vercel_sandbox。「コンテナ自体がセキュリティ境界なので、危険なコマンドの確認は飛ばされる」のは後ろの5つ。Docker の既定は権限を全部落としてから必要なものだけ戻し、権限昇格を禁じ、プロセス数に上限を置く
  • Codex の2軸: sandbox_moderead-only / workspace-write / danger-full-access の3つに加えて自作の権限プロファイルが置ける、approval_policyuntrusted / on-request / never ほか。既定の組み合わせは workspace-write + on-request。workspace-write では「ネットワークは既定で切れている」ので、要るなら [sandbox_workspace_write]network_access = true を立てる。OS 側の実装は macOS が sandbox-exec による Seatbelt、Linux が bwrap と seccomp、Windows は専用のもの
  • Pi がサンドボックスを持たない理由: 「部分的な in-process サンドボックスは、セキュリティ境界だと誤解されやすいわりに、結局ホストのシェル、ファイルシステム、パッケージ管理、資格情報、そして拡張のコードに依存し続ける。本物の隔離は、OS か仮想化/コンテナの境界から来る必要がある」。代わりにコンテナ、マイクロ VM への routing、方針つきサンドボックスの3通りを文書化している
  • 依存の隔離は境界ではない: Nix を使う開発環境の道具は、パッケージと環境の隔離を提供するもので、コンテナや VM とは隔離の型が違う。必要ならコンテナ定義を出力できる、という位置づけになっている
  • 出口の限界: 「既定では組み込みの代理サーバは外向きの通信の TLS を終端も検査もしないので、暗号化された接続の中身は見ていない」「github.com のような広いドメインを許すと、データ持ち出しの経路を作りうる」。終端させる network.tlsTerminate は試験的な設定として後から入ったが、同じ節に「中身の絞り込みを足すものではない」と書かれている
  • プロンプトインジェクション: Pi は「確実に防ぐことはできない」と明記。Hermes はコンテキストファイルの走査を持つが、疑わしいものに印を付けるもので、あらゆる場合の防止を保証していない
  • 隔離しても送られる: 「隔離は、モデルに何が送られるかを変えない。あなたのプロンプトと Claude が読んだファイルは、サンドボックスの有無に関わらず送信される」

簡略化したこと

  • どれも動かしていない: 設定の書式も既定値も、ドキュメントから読み取ったもので、手元で試していない
  • 測っていない: 隔離を入れたときの遅さも、どこまで実際に止まるかも、確かめていない。破れるかどうかを試すのは、この本の範囲を超える
  • 提供者ごとの違いに踏み込んでいない: クラウド側の隔離環境は複数あるが、どれがどういう境界を引いているかは比べていない
  • 組織で強制する話を浅くしている: 管理された設定で全員に強制する形はあるが、配り方には触れていない
  • 供給網に触れていない: 拡張やスキルやパッケージが持ち込むコードの危険は、隔離とは別の話になる。ここでは扱わない
  • 版が動く: 設定の名前も既定も版で変わる。ここに書いたのは執筆時点のもの

参考資料