执行策略 · Starlark 规则与提权

规则文件是一段受限的 Starlark 程序,只能调用 prefix_rule、network_rule、host_executable 三个内置函数;解析器边执行边收集规则,按命令首词建索引。匹配是逐词的前缀比较,多条命中取最严格的决定,没命中的交给内置启发式。结果变成审批判定:forbidden 直接拒绝,prompt 弹审批,每一段都被规则明确 allow 才跳过沙箱。在实验性的 zsh-fork 模式下,shell-escalation 还能在每一次 exec 时拦截子命令,按绝对路径重新判定并在沙箱外代为执行。

作者 David更新于 第 33 篇(共 57 篇)

执行策略 · Starlark 规则与提权

审批流程的第一关是“这条命令要不要问”。对命令来说,这一关的规则由 codex-execpolicy 这个小 crate 提供(去掉测试约 1800 行),内核里的 ExecPolicyManager(codex-rs/core/src/exec_policy.rs)负责加载、求值和写回。

用户看到的样子

在 ~/.codex/rules/ 或受信任项目的 .codex/rules/ 下放扩展名为 .rules 的文件,写 prefix_rule(pattern = [...], decision = "allow" | "prompt" | "forbidden");在审批框里选 Yes, and don't ask again for commands that start with ...,Codex 会自动往 ~/.codex/rules/default.rules 追加一条 allow 规则。隐藏子命令 codex execpolicy check 可以离线检验规则。完整写法见手册的命令规则,下面这段是仓库自带的示例,match 与 not_match 是随规则一起加载的内联测试:

prefix_rule(
    pattern = ["git", "reset", "--hard"],
    decision = "forbidden",
    justification = "destructive operation",
    match = [
        ["git", "reset", "--hard"],
    ],
    not_match = [
        ["git", "reset", "--keep"],
        "git reset --merge",
    ],
)

(codex-rs/execpolicy/examples/example.codexpolicy:4)

规则文件怎么变成 Policy

load_exec_policy(codex-rs/core/src/exec_policy.rs:662)从低优先级到高优先级遍历配置层,读每层配置目录下 rules/ 里的 .rules 文件(同一目录按文件名排序);codex exec --ignore-rules 会跳过用户层与项目层。所有文件交给同一个 PolicyParser,最后再叠上托管 requirements.toml 的 [rules](merge_overlay)。

解析器用 starlark crate 的扩展方言执行每个文件,只注入三个内置函数,都在 policy_builtins 里定义:

    fn prefix_rule<'v>(
        pattern: UnpackList<Value<'v>>,
        decision: Option<&'v str>,
        r#match: Option<UnpackList<Value<'v>>>,
        not_match: Option<UnpackList<Value<'v>>>,
        justification: Option<&'v str>,
        eval: &mut Evaluator<'v, '_, '_>,
    ) -> anyhow::Result<NoneType> {
        let decision = match decision {
            Some(raw) => Decision::parse(raw)?,
            None => Decision::Allow,
        };

(codex-rs/execpolicy/src/parser.rs:349)

  • prefix_rule:decision 缺省为 allow;justification 不能是空串;pattern 的每个元素是字符串或字符串列表(多选一),第一个元素是列表时会展开成多条规则,因为规则按命令首词建索引(MultiMap<String, RuleRef>)。match、not_match 的示例在整个文件执行完后才校验,写错的规则在加载时就报错,并带上文件与行号。
  • network_rule(host, protocol, decision, justification):给网络代理用的主机规则,protocol 取 http、https、socks5_tcp、socks5_udp,decision 可写 deny(等同 forbidden);主机名不许带通配符、协议头或路径。它主要由 Codex 自己写入:在网络审批里选“以后都允许”或“以后都拦截这个主机”时,追加进 default.rules 的就是这种规则。
  • host_executable(name, paths):限定哪些绝对路径可以回退到按文件名匹配,路径必须是绝对路径且文件名与 name 一致。

文件执行时没有副作用,不读写文件系统,唯一的产出就是这张规则表。解析失败时,交互会话发一条警告,只保留托管规则继续运行(load_exec_policy_with_warning)。

前缀匹配,取最严格

    pub fn matches_prefix(&self, cmd: &[String]) -> Option<Vec<String>> {
        let pattern_length = self.rest.len() + 1;
        if cmd.len() < pattern_length || cmd[0] != self.first.as_ref() {
            return None;
        }

        for (pattern_token, cmd_token) in self.rest.iter().zip(&cmd[1..pattern_length]) {
            if !pattern_token.matches(cmd_token) {
                return None;
            }
        }

        Some(cmd[..pattern_length].to_vec())
    }

(codex-rs/execpolicy/src/rule.rs:46)

逐词精确比较,命令可以比模式长,没有正则也没有通配。首词是绝对路径(如 /usr/bin/git)且没有精确规则时,按文件名 git 再找一遍,前提是 host_executable 没有限制这个路径。多条规则同时命中时取最严格的:Decision 枚举按 Allow、Prompt、Forbidden 的顺序派生了 Ord(codex-rs/execpolicy/src/decision.rs:9),Evaluation::from_matches 直接对命中的决定取 max()。与 OpenCode 那种“最后一条匹配说了算”不同,这里书写顺序无关紧要,严格的规则永远压过宽松的。

判定怎么变成审批

判定前先拆段(commands_for_exec_policy_for_platform):bash -lc "..." 这类调用里的脚本,若只由普通单词和 &&、||、;、| 组成,就用 tree-sitter 拆成多条命令分别判定,Windows 上对 PowerShell 的 -Command 也有类似处理(拆分细节见读懂一条命令)。每一段先查规则,没命中的交给启发式兜底(以 on-request 为例:危险命令要问,申请越出沙箱要问,其余交给沙箱),整体仍取最严格的结果:

  • Forbidden 直接拒绝,理由里带上规则的 justification;
  • Prompt 变成需要审批,但审批策略是 never 或 granular 关了对应开关时,改为拒绝;
  • Allow 变成不问,而且只有每一段都被规则明确放行时才跳过沙箱:
            Decision::Allow => ExecApprovalRequirement::Skip {
                // Bypass sandbox only when every parsed command segment is
                // explicitly allowed by execpolicy.
                bypass_sandbox: commands.iter().all(|command| {
                    exec_policy
                        .matches_for_command_with_options(
                            command,
                            /*heuristics_fallback*/ None,
                            &match_options,
                        )
                        .iter()
                        .any(|rule_match| {
                            is_policy_match(rule_match) && rule_match.decision() == Decision::Allow
                        })
                }),

(codex-rs/core/src/exec_policy.rs:440)

需要审批时,Codex 顺带算一条“建议规则”放进审批请求:优先用模型给的 prefix_rule,但它不能出现在 BANNED_PREFIX_SUGGESTIONS 里(python、bash -lc、node -e、git、rm、sudo 等能执行任意代码或过于宽泛的前缀),而且加上它之后必须能放行这条命令的每一段;否则退而取启发式判为“要问”的那一段命令。用户接受后,blocking_append_allow_prefix_rule 在文件锁保护下往 default.rules 追加一行 prefix_rule(pattern=[...], decision="allow"),内存里的策略经 ArcSwap 原子替换,下一条命令立刻生效。还有一个少见的例外:模型的 model_specialty 是 cyber,或托管要求的 auto_review.ignore_rules 列出了当前模型时,所有 allow 规则被忽略(AllowPrefixRules::IgnoreForCyberModel),prompt 与 forbidden 照常生效。

提权通道:拦截每一次 exec

前缀规则只看得到模型交来的那条命令。脚本里再调用的子命令,要到 exec 那一刻才现形。codex-shell-escalation 为此提供了一条拦截通道,只在开发中的 shell_zsh_fork 模式下启用(默认关闭,要求 Unix、用户 shell 为 zsh,并使用打过补丁的 zsh):

图表加载中…

zsh 的补丁给 exec 加了 EXEC_WRAPPER 支持:设置了这个环境变量,每次 exec 都先运行 codex-execve-wrapper(又是 codex 本身的一个 arg0 别名)。包装器从环境变量 CODEX_ESCALATE_SOCKET 拿到继承下来的套接字,先发一个握手数据报,附带一对新建套接字的一端,此后的请求与响应都走这对专用套接字,于是服务端能同时处理多个并发的 exec。服务端 CoreShellActionProvider::determine_action 用程序的绝对路径查规则、没命中走启发式,需要时发起审批(请求里带 approval_id,客户端据此区分同一条命令下的多个子命令审批),再给出 Run、Escalate 或 Deny(codex-rs/shell-escalation/src/unix/escalate_protocol.rs:37)。Run 表示就在当前沙箱里照常 exec;Escalate 时包装器把自己的标准输入输出复制一份,经 SCM_RIGHTS 交给服务端,由 Codex 按 EscalationExecution 的三种方式之一重新运行这个程序:完全不套沙箱(Unsandboxed)、用本轮的沙箱配置(TurnDefault)、或用请求附带的权限(Permissions),跑完把退出码传回,包装器以同样的退出码结束。于是“规则明确放行的子命令在沙箱外跑、其余留在沙箱里”可以细化到一条脚本内部的每一次 exec。

codex execpolicy check

这个子命令在 codex --help 里是隐藏的,参数定义在 ExecPolicyCheckCommand(codex-rs/execpolicy/src/execpolicycheck.rs:17):-r/--rules 可重复,--pretty 美化输出,--resolve-host-executables 打开绝对路径到文件名的回退。它只输出命中的规则与最严格的决定,既不拆分 bash -lc 脚本,也不跑启发式,所以结果是“规则怎么看这条参数列表”,不完全等于运行时的判定。

和《从 LLM 到 Coding Agent》对照

那本书的权限系统第二层是“工具名加参数模式”的规则,比如 Bash(git status) 永远放行。Codex 把这一层做成了真正的程序:规则是 Starlark,自带内联测试,按参数列表逐词匹配,取最严格而不是最后一条;启发式兜底和“每段都放行才跳过沙箱”保证复合命令里夹带的危险片段不会被一条宽松规则顺带放过。OpenCode 的权限系统对 bash 同样按命令前缀记“总是允许”,但只存在内存里,重启即失效;Codex 的规则落盘在 default.rules,跨会话生效。


上一篇:网络代理 · 联网权限怎么落地 · 下一篇:自动审查 · 让另一个模型替你审批

本页目录