Skip to content

命令分類 (execpolicy)

源码版本rust-v0.145.0

execpolicy crate 是 codex 的命令分類引擎:給定一條 argv,它判斷該 allow、prompt 還是 forbidden。規則用 Starlark 寫在 rules/ 目錄,執行時 PolicyParser 解析成 Policy,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 命中規則。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 編譯成 Policy,PrefixRuleNetworkRule 做精確匹配,未命中的走 core 側啟發式回退。Decision 三值讓「允許/審批/禁止」在同一套語意裡表達,規則衝突時最嚴判決贏。理解這條鏈路對除錯「為什麼這條命令要審批」和「使用者存了規則為什麼沒生效」很關鍵,相關執行落地在 命令執行 exec-server跨平台沙箱