Skip to content

Command Classification (execpolicy)

源码版本rust-v0.145.0

The execpolicy crate is codex's command classification engine: given an argv, it decides whether to allow, prompt, or forbid. Rules are written in Starlark in the rules/ directory; at runtime PolicyParser compiles them into a Policy, and Policy::check runs before each command. core's ExecPolicyManager layers approval policy and heuristic fallback on top.

Responsibilities

  1. Express each rule's verdict with a three-valued Decision enum: codex-rs/execpolicy/src/decision.rs:9-27
  2. Use PrefixRule / NetworkRule for prefix matching and network domain matching: codex-rs/execpolicy/src/rule.rs:40-115
  3. The Starlark parser compiles policy files into a Policy: codex-rs/execpolicy/src/parser.rs:38-83
  4. Core-side ExecPolicyManager merges the policy result with approval policy and dangerous-command heuristics: codex-rs/core/src/exec_policy.rs:312-410
  5. For commands that no rule matches, derive a default Decision from profile and command source: codex-rs/core/src/exec_policy.rs:728-760

Design motivation

Why not regex or glob? Because the command structure itself is argv, and prefix matching is more precise than string regex — and easier to write back. After the user clicks "always allow", blocking_append_allow_prefix_rule directly appends a line prefix_rule(pattern=["npm", "run"], decision="allow") to the policy file; the same argv hits the rule next time. PrefixPattern supports two token types, Single and Alts ([a|b]), covering exactly the "prefix + choose-one-of-two args" case.

Why Starlark instead of TOML? Because policy needs conditional logic ("allow only if host is in some list"), needs functions (prefix_rule, network_rule), and TOML cannot express that. Starlark is a Python subset; policy files are highly readable and can be safely evaluated by the Rust starlark crate. PolicyParser binds the AST and PolicyBuilder together with RefCell; after parsing, build produces an immutable Policy in one shot.

Decision is three-valued rather than two-valued to distinguish "explicitly forbidden" from "needs approval". forbidden blocks outright, prompt triggers the user-approval UI, allow skips approval. When multiple rules hit, the max wins — Allow < Prompt < Forbidden; the strictest verdict wins.

Key files

Decision is three-valued rather than two-valued to distinguish "explicitly forbidden" from "needs approval".

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,
}

Prefix matching is more precise than string regex and easier to write back — after the user clicks "always allow", blocking_append_allow_prefix_rule directly composes a Starlark call and appends it to the policy file.

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 also layers heuristics on top of the policy verdict: when no rule matches, render_decision_for_unmatched_command looks at is_known_safe_command, profile_has_managed_filesystem_restrictions, and so on to derive the default 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 => /* 直接放行 */,
}

Data flow

Boundaries and failures

Summary

execpolicy is the core of codex's command classification: Starlark policy compiles into Policy; PrefixRule and NetworkRule do precise matching; unmatched commands go through core's heuristic fallback. The three-valued Decision lets "allow / approve / forbid" be expressed in one semantics, and the strictest verdict wins on conflict. Understanding this chain is key to debugging "why does this command need approval" and "why didn't the user-saved rule take effect". The actual execution lands in command execution exec-server and cross-platform sandbox.