Skip to content

命令分类 (execpolicy)

源码版本rust-v0.145.0

execpolicy crate 是 codex 的命令分类引擎:给定一条 argv,它判断该 allow、prompt 还是 forbidden。规则用 Starlark 写在 rules/ 目录,运行时 PolicyParser 解析成 PolicyPolicy::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 文件编译成 Policycodex-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 命中规则。PrefixPattern 支持 SingleAlts[a|b])两种 token,正好覆盖"前缀 + 二选一参数"的场景。

为什么是 Starlark 而不是 TOML?因为 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 => /* 直接放行 */,
}

数据流

边界与失败

小结

execpolicy 是 codex 命令分类的核心:Starlark policy 编译成 PolicyPrefixRuleNetworkRule 做精确匹配,未命中的走 core 侧启发式回退。Decision 三值让"允许/审批/禁止"在同一套语义里表达,规则冲突时最严判决赢。理解这条链路对调试"为什么这条命令要审批"和"用户存了规则为什么没生效"很关键,相关执行落地在 命令执行 exec-server跨平台沙箱