Skip to content

コマンド分類 (execpolicy)

源码版本rust-v0.145.0

execpolicy crate は codex のコマンド分類エンジンだ:argv を与えられると、allow、prompt、forbidden のいずれかを判定する。ルールは Starlark で rules/ ディレクトリに書かれ、実行時に PolicyParserPolicy にパースし、Policy::check が各コマンドの前に一回走る。core の ExecPolicyManager はその上に承認ポリシーとヒューリスティックフォールバックを重ねる。

責務

  1. Decision の三値列挙型で各ルールの判決を表す:codex-rs/execpolicy/src/decision.rs:9-27
  2. PrefixRule / NetworkRule の二種のルールで前缀マッチとネットワークドメインマッチを行う:codex-rs/execpolicy/src/rule.rs:40-115
  3. Starlark パーサーが policy ファイルを Policy にコンパイルする:codex-rs/execpolicy/src/parser.rs:38-83
  4. core 側の ExecPolicyManager が policy 結果に承認ポリシーと危険コマンドヒューリスティックを重ねる:codex-rs/core/src/exec_policy.rs:312-410
  5. ルールにマッチしなかったコマンドは profile とコマンド出所からデフォルト Decision を導出する:codex-rs/core/src/exec_policy.rs:728-760

設計動機

なぜ正規表現や glob を使わないのか?コマンド構造自体が argv で、前缀マッチは文字列正規表現より正確で、書き戻しも簡単だからだ——ユーザが「今後は常に許可」をクリックした後、blocking_append_allow_prefix_rule が policy ファイルに prefix_rule(pattern=["npm", "run"], decision="allow") の一行を追記し、次回同じ argv がルールにヒットする。PrefixPatternSingleAlts([a|b])の二つの token をサポートし、「前缀 + 二択引数」のシーンをちょうど覆う。

なぜ TOML ではなく Starlark なのか?policy には条件ロジック(「host があるリストに含まれる場合だけ allow」)と関数(prefix_rulenetwork_rule)が必要で、TOML では表現できないからだ。Starlark は Python のサブセットで、ポリシーファイルの可読性が高く、Rust 側の starlark crate で安全に評価できる。PolicyParser は AST と PolicyBuilderRefCell で結び、パース完了後に一回で build して不変な Policy にする。

Decision が二値ではなく三値なのは、「明示的な禁止」と「承認が必要」を区別するためだ。forbidden は直接ブロックし、prompt はユーザ承認 UI を発火し、allow は承認をスキップする。複数のルールが同時にヒットした場合は max を取る——Allow < Prompt < Forbidden で、最も厳しい判決が勝つ。

主要ファイル

Decision が二値ではなく三値なのは、「明示的な禁止」と「承認が必要」を区別するためだ。

rust
pub enum Decision {
    /// Command may run without further approval.
    Allow,
    /// Request explicit user approval; rejected outright when running with `approval_policy="never"`.
    Prompt,
    /// Command is blocked without further consideration.
    Forbidden,
}

前缀マッチは文字列正規表現より正確で、書き戻しも簡単だ——ユーザが「今後は常に許可」をクリックすると、blocking_append_allow_prefix_rule が Starlark 呼び出し一行を組み立てて policy ファイルに追記する。

rust
pub fn blocking_append_allow_prefix_rule(
    policy_path: &Path,
    prefix: &[String],
) -> Result<(), AmendError> {
    if prefix.is_empty() {
        return Err(AmendError::EmptyPrefix);
    }
    let tokens = prefix
        .iter()
        .map(serde_json::to_string)
        .collect::<Result<Vec<_>, _>>()
        .map_err(|source| AmendError::SerializePrefix { source })?;
    let pattern = format!("[{}]", tokens.join(", "));
    let rule = format!(r#"prefix_rule(pattern={pattern}, decision="allow")"#);
    append_rule_line(policy_path, &rule)
}

core は policy の判決を取った後にヒューリスティックを重ねる:どのルールにもヒットしなかった場合、render_decision_for_unmatched_commandis_known_safe_commandprofile_has_managed_filesystem_restrictions などの条件を見てデフォルト Decision を導出する。

rust
let evaluation = exec_policy.check_multiple_with_options(
    commands.iter(),
    &exec_policy_fallback,
    &match_options,
);
// ...
match evaluation.decision {
    Decision::Forbidden => ExecApprovalRequirement::Forbidden { reason: /* ... */ },
    Decision::Prompt => { /* 走审批流程 */ }
    Decision::Allow => /* 直接放行 */,
}

データフロー

境界と失敗

  • parse_shell_lc_plain_commands は単純な shell しか処理できない。heredoc/リダイレクト/パイプは used_complex_parsing=true になり、ルールの自動派生修正は許可されない:codex-rs/core/src/exec_policy.rs:325-353
  • 複数ルールが衝突した場合、from_matchesmax() を取り、Allow < Prompt < Forbidden で最も厳しい判決が勝つ:codex-rs/execpolicy/src/policy.rs:357-374
  • network_rule の host に scheme や path を含めることはできず、normalize_network_rule_hosthttps://example.com/foo のような書き方を拒否する:codex-rs/execpolicy/src/rule.rs:156-189
  • ヒューリスティックフォールバック render_decision_for_unmatched_commandwindows_sandbox_level と profile の managed FS 制限を見る——Windows sandbox が切られていれば、profile がどれだけ厳しく書かれていても保守的なパスに進む:codex-rs/core/src/exec_policy.rs:751-757
  • canonicalize_command_for_approvalbash -lc '...' 系コマンドに __codex_shell_script__ 前缀を付け、wrapper のパス差異でルールがヒットしないのを防ぐ:codex-rs/core/src/command_canonicalization.rs:21-35

まとめ

execpolicy は codex のコマンド分類の核心だ:Starlark policy を Policy にコンパイルし、PrefixRuleNetworkRule で正確なマッチを行い、ヒットしなかったものは core 側のヒューリスティックフォールバックに回す。Decision の三値で「許可/承認/禁止」を同一のセマンティクスで表し、ルール衝突時は最も厳しい判決が勝つ。このリンクの理解は「なぜこのコマンドが承認を要求するのか」「ユーザが保存したルールがなぜ効かないのか」をデバッグするのに重要で、関連する実行の着地は コマンド実行 exec-serverクロスプラットフォームサンドボックス にある。