Security 2026年8月28日

第3回 Codex の権限設計 — 承認と隔離を2軸で決める

Codex の approval_policy と sandbox_mode は独立した2軸で、掛け算で実効的な安全度が決まります。作業の種類ごとにどの組み合わせを選ぶか、OS によって隔離レベルが変わる落とし穴、ネットワーク到達の扱い、Claude Code の permissions との対応関係を整理します。

難易度基本的な操作を一度試したことがある前提です種別学習コース

この回のキーメッセージ

Codex の安全度は「何ができるか」と「いつ聞くか」の掛け算で決まります。どちらか一方だけを締めても、もう一方が緩ければ実効的な防御にはなりません。

Codex の設定を初めて触ると、approval_policysandbox_mode という似た顔をした 2 つのキーが並んでいて、どちらを触ればいいのか迷います。よくある失敗は、承認プロンプトが煩わしいので approval_policy = "never" にし、そのついでに「どうせ確認しないなら制限も外そう」と sandbox_mode = "danger-full-access" にしてしまうことです。この 2 つは同時に緩めるべきものではありません。

各モードの仕様そのものは Codex CLI 承認モード&サンドボックス に整理してあります。この回では仕様を繰り返さず、自分の環境と作業内容に対してどの組み合わせを選ぶか、そして選んだ設定が本当にその通り効いているかを確認する手順を書きます。

前提として、第1回で扱った 3 層モデル(AIで作る時代の攻撃面)のうち、ここで守っているのは一番手前の層です。守りたいのは手元のマシンにある認証情報・鍵・他リポジトリのソースであって、生成されるコードの品質ではありません。


1. 2軸で考える

2 つのキーは、答えている問いが違います。

キー答えている問い効いている場所
sandbox_mode技術的に何ができるかOS レベルの実行制限
approval_policyいつ人間に聞くかCodex のプロンプト制御

重要なのは、approval_policy はサンドボックスを緩めないという点です。never にしても read-only サンドボックスの中で書き込みが通ることはありません。逆に danger-full-access にしても untrusted なら実行前に確認は入ります。この独立性があるので、両者の掛け算で実効的な安全度を設計できます。

3×3 のマトリクス

untrusted(信頼済み以外は確認)on-request(境界を越えるとき確認)never(確認しない)
read-only最も固い。読むだけ。誤爆の余地がほぼない読むだけ。書き込みが必要になった時点で相談が入る読むだけを無人で回せる。調査・要約の自動化に最適
workspace-write実装は進むが確認が頻繁で手が止まりやすい日常の実装作業の既定。ワークスペース外・ネットワークで止まるワークスペース内は無確認で書き換わる。ブランチと差分レビューが唯一の砦
danger-full-access制限は無いが毎回止まる。人が全部見る前提の一時措置制限が無いので「境界を越える」判定が働きにくく、確認が入る場面が減る実質的に無防備。下の説明を参照

danger-full-access × never が意味すること

この組み合わせは、「モデルが出力したコマンドを、人間の確認なしに、OS の制限なしに、そのユーザー権限で実行する」という状態です。具体的には次がすべて可能になります。

  • ワークスペース外のファイルの読み書き(~/.ssh~/.aws、他社案件のリポジトリを含む)
  • 任意の外部ホストへの通信
  • 上の 2 つの組み合わせ、つまり手元の認証情報を読んで外部へ送る操作

第5回で扱うプロンプトインジェクション(MCP・外部連携とプロンプトインジェクション)の観点で言うと、この設定は「機密データへのアクセス」「外部への通信能力」の 2 つを無条件に与えた状態です。あとは非信頼コンテンツ(issue 本文、依存パッケージの README、Web ページ)を 1 つ読ませるだけで条件が揃います。

使ってよいのは、捨てられるコンテナや VM の中だけです。ホストマシンで恒久設定にしないでください。


2. 作業の種類ごとの推奨組み合わせ

「常にこれ」という単一の正解はありません。作業の性質で切り替えます。

作業sandbox_modeapproval_policy理由
見知らぬリポジトリを読む・調査するread-onlynever読むだけなので確認する意味がない。他人のコードには非信頼な指示が混ざりうるため、書き込み能力を渡さない
自分のリポジトリで実装するworkspace-writeon-request作業ディレクトリ内は自由に動かし、外に出るときだけ止める
チームの共有リポジトリで実装するworkspace-writeuntrusted または on-request影響範囲が自分だけで閉じないので、承認側を一段固くする
CI から無人で回すworkspace-writenever人がいないので確認は無意味。代わりにサンドボックス側で完全に閉じる。ネットワークも切る
本番に触れる作業(マイグレーション、デプロイ)この組み合わせでは守れない。第12回の運用側の統制で扱う

最後の行は重要なので補足します。本番への到達は Codex の設定で守るものではありません。本番の認証情報を手元の開発環境に置かないこと、デプロイを CI 経由に限定することで守ります。エージェントの権限設計に本番防御まで背負わせると、設定を一段緩めた瞬間に本番が落ちます。

CI で never を選ぶときの条件

無人実行で approval_policy = "never" を選ぶのは避けられません。人がいないので on-request にすると単に止まるだけです。そのぶん、次の 3 つを満たしてから有効にします。

  1. 実行環境が使い捨てである(ジョブごとに破棄されるコンテナ)
  2. その環境に、そのジョブに必要な権限しか無い(本番の書き込み権限を持つトークンを置かない)
  3. 外部への到達が既定で切られている(次節)

3. OS によって実際の隔離レベルが変わる

ここが運用上いちばん見落とされる点です。Codex のサンドボックスは OS ネイティブの機構に依存しているため、同じ config.toml を配っても、実際に隔離が効くかどうかは環境ごとに違います

OS実装実務上の注意
macOSSeatbelt(sandbox-execOS 標準のため追加セットアップが不要。ここで動作確認して安心してしまいやすい
Linux / WSL2bubblewrap(bwrapbwrap の導入が必須。未導入だとサンドボックスが期待どおり立ち上がらない
WindowsPowerShell 実行時はネイティブ Windows サンドボックス、WSL2 実行時は Linux 実装同じマシンでも起動経路で実装が切り替わる

チーム展開でありがちな事故は、設定ファイルを macOS で作って検証し、そのまま Linux の CI イメージや WSL2 の開発機に配ることです。macOS では隔離されていた操作が、bwrap の無い環境では隔離されないまま通る可能性があります。

導入時のチェックは 1 行で済みます。

which bwrap

出力が空なら、その環境ではサンドボックスの前提が満たされていません。Debian 系なら sudo apt install bubblewrap、Fedora 系なら sudo dnf install bubblewrap で導入します。CI イメージの Dockerfile にも同じパッケージを入れることを忘れないでください。インストール手順とプラットフォーム別の詳細は 承認モード&サンドボックスの記事 にまとめてあります。

運用ルールとしては、次の形にしておくと事故が減ります。

  • 設定を配る側は「この設定は bwrap が入っている前提で隔離が成立する」と明記する
  • 受け取る側は初回起動前に which bwrap を通す
  • CI では、ジョブの先頭でサンドボックス前提が満たされていることを確認し、満たされなければジョブを失敗させる

「設定ファイルを配った=全員が同じ安全度になった」とは限らない、という前提で運用してください。


4. ネットワーク到達を既定で切る

サンドボックスがファイルシステムを閉じても、外部へ到達できるなら情報は出ていきます。逆に外部到達を切っておけば、仮にワークスペース内で不審な指示を踏んでも、持ち出しの経路がありません。無人実行では特に効きます。

ChatGPT Work 環境では、Settings > Data controls > Work network access で組織側の到達範囲を決めます。

状態到達できる範囲
許可公開インターネット
制限管理対象の許可リストに載ったホスト名のみ

desktop app / CLI / IDE の各サーフェスでも承認の扱いを選べます。設定変更は実行環境をリフレッシュしたあとに反映されるため、変更直後に「効いていない」と見えることがあります。判断の順序はシンプルです。

  1. 無人実行(CI、スケジュール実行)では、外部到達を切るのを既定にする
  2. パッケージ取得など到達が必要なら、必要なホストだけを許可リストに載せる
  3. 「全部許可して動かしてから絞る」をやらない。絞る作業は後回しにされて残らない

Codex CLI 側のサンドボックスも既定ではネットワークを許可しません。npm install が通らないときに、原因を確かめずサンドボックスごと danger-full-access に落とすのが最悪の対処です。必要なドメインだけを開ける方法は既存記事側に書いてあります。


5. Rules は「モードの隙間」を埋める

approval_policysandbox_mode の組み合わせだけでは、粒度が「全部確認するか、全部通すか」に寄ります。workspace-write × never で回しているとき、ワークスペース内の git push --force だけは止めたい、といった要求はモードでは表現できません。

その隙間を埋めるのが Codex Rules です。コマンドの先頭パターンに対して allow / prompt / forbidden を割り当てる仕組みで、approval_policy = "never" でも Rules 側の prompt / forbidden が優先されます。記法・ファイルの置き場所・優先順位の詳細は Codex Rules の記事 を参照してください。

権限設計の観点で押さえるのは 2 点だけです。

  • Rules はセキュリティ境界ではない。パターンマッチである以上、動的に組み立てられたコマンドを完全には捕捉できません。最終的な境界はサンドボックスと OS の権限です
  • forbidden から書き始める。最初から allow を並べると、書き漏らしがそのまま穴になります。禁止したいものを数個書き、頻出操作を順に allow へ移すほうが穴になりません

6. Claude Code との対応関係

第2回で扱った Claude Code の permissions(Claude Code の権限設計)と Codex は、語彙が違うだけで目的は同じです。両方を使う場合、頭の中で対応づけておくと設定の抜けに気づきやすくなります。

守りたいものCodexClaude Code
ファイル書き込みの範囲sandbox_mode = read-only / workspace-writeサンドボックス(作業ツリー外への書き込み不可)
機密ディレクトリの保護サンドボックスが既定でブロック既定では読めるsandbox.credentials の明示設定が要る
外部への到達サンドボックスのネットワーク制御、Work network accessサンドボックス外のプロキシ経由で制御。既定で許可ドメインなし
人に聞くかどうかapproval_policy = untrusted / on-request / neverpermissions の ask
特定操作の禁止Rules の forbiddenpermissions の deny
特定操作の自動許可Rules の allowpermissions の allow

思想の違いは、**Claude Code が「ツール単位のルール列挙」で、Codex が「モードの掛け算」**である点です。

  • Claude Code は Bash(npm:*) のようなパターンを allow / ask / deny の配列へ積み上げ、deny → ask → allow の順に評価します。表現力は高いぶん、列挙漏れがそのまま挙動になります
  • Codex は 2 つのモードで大枠を決め、細部を Rules で補います。大枠が先に決まるので、列挙漏れが即座に穴にはなりにくい代わりに、粒度を上げるには Rules を足す必要があります

どちらが優れているという話ではありません。両者とも守っている対象は同じで、手元の認証情報と、作業対象外のデータです。この観点で見ると、片方でやっている対策のうちもう片方に無いものが見つかります。たとえば Claude Code 側でネットワークを絞っているのに Codex 側では開けっぱなし、といった非対称は珍しくありません。


7. 設定例

日常の実装を既定にしつつ、調査用に固いプロファイルを 1 つ用意する形です。プロファイルは起動時に codex --profile readonly のように切り替えます。

# ~/.codex/config.toml

# 既定:自分のリポジトリでの実装作業
approval_policy = "on-request"
sandbox_mode    = "workspace-write"

[sandbox_workspace_write]
# ワークスペース外で書き込みを許すパスだけを明示的に列挙する。
# ここに ~ 直下や広いディレクトリを書かない。
writable_roots = ["/var/log/myapp"]

# 見知らぬリポジトリの調査用:読むだけなので確認は求めない
[profiles.readonly]
approval_policy = "never"
sandbox_mode    = "read-only"

このファイルで意図的にやっていないことが 2 つあります。

  • danger-full-access のプロファイルを用意していません。用意すると必ず使われます。必要になったらそのつど CLI フラグで、使い捨て環境の中でだけ指定します
  • writable_roots を広く取っていません。ここは「便利だから」で足しやすい場所ですが、足すたびにサンドボックスの意味が薄くなります

設定を変えたら、意図どおりに止まるかを一度確認します。read-only プロファイルでワークスペース内のファイル編集を頼み、拒否されることを見ておくだけで十分です。止まるはずのものが止まることを一度も確認していない設定は、設定していないのと同じです。


まとめ

  • sandbox_mode は「何ができるか」、approval_policy は「いつ聞くか」。独立した 2 軸で、掛け算で実効的な安全度が決まる
  • danger-full-access × never は、手元の認証情報を読んで外部へ送る操作までを無確認で許す状態。使い捨て環境の中だけに限定する
  • 調査は read-only × never、実装は workspace-write × on-request、CI は workspace-write × never + 外部到達を切る、が出発点
  • Linux / WSL2 では bwrap が入っていないと隔離の前提が崩れる。同じ設定を配っても OS で実際の隔離レベルが違う。導入時に which bwrap を通す
  • Claude Code の permissions と守っている対象は同じ。対応表で見比べると、片方だけ緩い箇所が見つかる

次回は、モードや permissions では表現しきれない条件判定を Hooks で組む方法を扱います。


参考リンク