openinterpreter 的 execpolicy 详解:用 Starlark 编写 prefix_rule 命令策略、host_executable 匹配语义与 codex execpolicy check CLI
execpolicy 是 openinterpreter(Codex 系 CLI)仓库中的一个策略引擎与命令行工具,用于在 Agent 执行 shell 命令之前,基于可版本化的策略文件决定该命令应当放行(allow)、请求用户审批(prompt)还是直接禁止(forbidden)。本篇基于仓库内 codex-rs/execpolicy/README.md 完整覆盖其策略语法、匹配语义与 CLI 用法,并结合 src/parser.rs、src/policy.rs、src/rule.rs 等源码,补充解析与匹配阶段的实现细节,帮助读者既会写策略文件,也理解判定结果的产生机制。
一、execpolicy 是什么:prefix-rule 策略引擎概览
根据 README 的 Overview,execpolicy 围绕两个 Starlark 内置函数构建:
prefix_rule(pattern=[...], decision?, justification?, match?, not_match?):按命令 token 前缀匹配规则;host_executable(name=..., paths=[...]):主机可执行文件元数据,用于约束哪些绝对路径可以走 basename 回退匹配。
需要明确的边界:当前版本覆盖的是 execpolicy 语言的 prefix-rule 子集加主机可执行文件元数据,README 明确说明更丰富的语言(a richer language)会后续跟进。策略文件本身是 Starlark 语法,由 parser.rs 中的 PolicyParser 基于 starlark crate 解析——parse 方法使用 Dialect::Extended(并开启 f-string)构建 AST,再用 GlobalsBuilder 注入 policy_builtins 后求值,求值过程中通过 Evaluator.extra 持有 PolicyBuilder 累积规则。
几个核心匹配约定:
- token 按顺序匹配,
pattern中的任意元素可以是列表,表示“多选一”的备选 token; decision默认为allow,合法取值为allow、prompt、forbidden;justification是可选的人类可读理由,可为任意decision提供,并在不同上下文(审批提示、拒绝消息)中被展示。README 特别建议:当decision = "forbidden"时,若合适应在justification中给出推荐替代方案,例如"Use `jj` instead of `git`.";match/not_match提供加载期校验的调用示例(可视为单元测试):示例可以是 token 数组,也可以是字符串(字符串会用shlex分词);- CLI 始终打印评估结果的 JSON 序列化;
- 旧版规则匹配器位于 codex-execpolicy-legacy 独立 crate 中,与新的 prefix-rule 引擎并存。
从源码结构看,Decision 在 src/decision.rs 中定义为 Allow / Prompt / Forbidden 三值枚举,且派生了 Ord:源码注释指出 Prompt 表示“请求显式用户审批;当以 approval_policy="never" 运行时会被直接拒绝”,Forbidden 表示“不再经过任何考虑直接阻断”。这个严重程度排序正是后文“取最严格判定”的基础。
二、prefix_rule 语法:参数、默认值与示例校验
README 给出的完整策略形状如下:
prefix_rule(
pattern = ["cmd", ["alt1", "alt2"]], # ordered tokens; list entries denote alternatives
decision = "prompt", # allow | prompt | forbidden; defaults to allow
justification = "explain why this rule exists",
match = [["cmd", "alt1"], "cmd alt2"], # examples that must match this rule
not_match = [["cmd", "oops"], "cmd alt3"], # examples that must not match this rule
)
各参数在实现层的约束(均可在 src/parser.rs 的 prefix_rule builtin 中找到对应校验逻辑):
| 参数 | 必填 | 类型 | 说明与校验 |
|---|---|---|---|
pattern |
是 | token 列表 | 有序 token;元素可为字符串或字符串列表(备选)。解析函数 parse_pattern 要求列表非空;parse_pattern_token 要求备选列表非空且元素均为字符串,否则报 InvalidPattern。含多个备选的首 token 会在解析期展开为多条 PrefixRule(见下文源码分析) |
decision |
否 | allow / prompt / forbidden |
缺省为 allow;取值经 Decision::parse 校验,非法值报 InvalidDecision |
justification |
否 | 字符串 | 不能为纯空白,否则报 justification cannot be empty |
match |
否 | 示例列表 | 每个示例为 token 数组或字符串;字符串示例用 shlex::split 分词,非法 shell 语法会报 InvalidExample。加载期必须命中本规则,否则报 ExampleDidNotMatch |
not_match |
否 | 示例列表 | 同上格式;加载期不得命中任何规则,否则报 ExampleDidMatch |
仓库自带一份语法示例策略 examples/example.codexpolicy(其头部注释说明“仅用于演示语法,不推荐实际使用”),展示了三类典型用法:
prefix_rule(
pattern = ["git", "reset", "--hard"],
decision = "forbidden",
justification = "destructive operation",
match = [
["git", "reset", "--hard"],
],
not_match = [
["git", "reset", "--keep"],
"git reset --merge",
],
)
prefix_rule(
pattern = ["ls"],
match = [
["ls"],
["ls", "-l"],
["ls", "-a", "."],
],
)
prefix_rule(
pattern = ["cp"],
decision = "prompt",
match = [
["cp", "foo", "bar"],
"cp -r src dest",
],
)
其中 ls 规则省略 decision,即演示了默认 allow 的行为;git reset --hard 规则则演示了 forbidden + justification + not_match 的完整组合。
加载期示例校验:把 match/not_match 当单元测试
match / not_match 的校验发生在策略加载阶段,这是 README 中 “validated at load time” 的实现。从源码结构看,prefix_rule builtin 不会立即校验示例,而是把 (rules, matches, not_matches, location) 记录为 pending_example_validation;PolicyParser::parse 在模块求值完成后调用 validate_pending_examples_from,用这些规则构造临时 Policy,分别执行:
validate_not_match_examples(src/rule.rs):任一负例被任一规则命中即报ExampleDidMatch;validate_match_examples(src/rule.rs):任一正例未命中任何规则即报ExampleDidNotMatch。
校验错误会附加 Starlark 调用位置(文件、行列区间),方便定位到策略文件中的具体规则。值得注意的是,示例校验内部以 resolve_host_executables: true 的 MatchOptions 运行,即示例校验不受 basename 回退开关限制。
三、host_executable 与 basename 回退匹配语义
host_executable 用于声明某个 basename 规则只允许对哪些绝对路径生效:
host_executable(
name = "git",
paths = [
"/opt/homebrew/bin/git",
"/usr/bin/git",
],
)
README 列出的四条匹配语义是理解 basename 回退的关键:
- execpolicy 总是先尝试对首 token 做精确匹配;
- 在 basename 回退(host-executable resolution)未启用时,
/usr/bin/git status只会匹配首 token 恰好是/usr/bin/git的规则; - 启用后,若没有精确规则命中,execpolicy 可以把
/usr/bin/git回退到针对git的 basename 规则; - 若存在
host_executable(name="git", ...),basename 回退只允许其列出的绝对路径; - 若某 basename 没有
host_executable()条目,则允许 basename 回退。
这套语义在 src/policy.rs 的 matches_for_command_with_options 中清晰可见:先执行 match_exact_rules(按命令首 token 从 rules_by_program 多值映射中取规则并逐条前缀匹配);只有当精确匹配为空、且 MatchOptions::resolve_host_executables 为真时,才走 match_host_executable_rules(src/policy.rs)。后者要求首 token 是绝对路径,取其 basename 后查规则表,并做 host_executables_by_name 白名单检查——若该 basename 注册过路径列表而当前程序不在列表中,直接返回空。命中后,匹配结果通过 with_resolved_program 附上原始绝对路径,这就是响应中 resolvedProgram 字段的来源。
解析侧同样有严格校验(src/parser.rs 的 host_executable builtin):
name必须是非空裸可执行名(不允许路径分隔);paths每项必须是字符串且为绝对路径;- 每个路径的 basename 必须与
name一致,否则报host_executable path ... must have basename ...; - 重复路径会被去重。
跨平台细节在 src/executable_name.rs:在 Windows 上查找键会统一小写并剥离 .exe、.cmd、.bat、.com 后缀,因此 git.exe 与 git 归一到同一 basename。
四、CLI 实操:codex execpolicy check
README 的 CLI 部分给出了完整用法。从 Codex CLI 运行 codex execpolicy check 子命令,传入一个或多个策略文件(例如 src/default.rules)和待检命令:
codex execpolicy check --rules path/to/policy.rules git status
为绝对程序路径启用 basename 回退,传 --resolve-host-executables:
codex execpolicy check \
--rules path/to/policy.rules \
--resolve-host-executables \
/usr/bin/git status
其他操作要点:
- 可传多个
--rules标志合并规则,按提供顺序加载与评估;--pretty输出格式化 JSON; - 开发时也可以直接运行独立 dev 二进制:
cargo run -p codex-execpolicy -- check --rules path/to/policy.rules git status
- 示例结果:命中规则输出
{"matchedRules":[{...}],"decision":"allow"};未命中任何规则输出{"matchedRules":[]}。
命令参数的源码级说明
独立二进制的参数定义在 src/execpolicycheck.rs 的 ExecPolicyCheckCommand 中,与 README 描述一一对应:
--rules/-r:可重复的路径参数,必填;--pretty:serde_json::to_string_pretty输出,否则为紧凑单行 JSON;--resolve-host-executables:对应MatchOptions { resolve_host_executables },即上文 basename 回退开关;- 命令 token:
trailing_var_arg+allow_hyphen_values,因此git reset --hard这类带连字符的参数会被原样收进 token 列表,而不会被 clap 误认为选项。
“合并规则、按顺序评估”的实现:load_policies(src/execpolicycheck.rs)顺序读取并解析每个文件,共用同一个 PolicyParser 后统一 build() 成一个 Policy,因此文件间规则天然合并;评估时同一程序名的规则按插入顺序存放于 MultiMap。CLI 路径不传 heuristics 回退(None),所以“无规则命中”就是空的 matchedRules,与独立策略文件的使用场景一致。
此外,仓库还提供了策略文件的程序化追加能力:src/lib.rs 导出 blocking_append_allow_prefix_rule 与 blocking_append_network_rule,其中前者会自动去重——tests/basic.rs 的 append_allow_prefix_rule_dedupes_existing_rule 测试验证了对同一前缀追加两次后,文件内容仍只保留一条 prefix_rule(pattern=["python3"], decision="allow")。
五、响应结构:matchedRules 与有效 decision
README 给出的响应形状:
{
"matchedRules": [
{
"prefixRuleMatch": {
"matchedPrefix": ["<token>", "..."],
"decision": "allow|prompt|forbidden",
"resolvedProgram": "/absolute/path/to/program",
"justification": "..."
}
}
],
"decision": "allow|prompt|forbidden"
}
对应规则与实现:
- 没有规则命中时,
matchedRules为空数组且decision被省略——对应 src/execpolicycheck.rs 中decision字段skip_serializing_if = "Option::is_none"且取值来自matched_rules.iter().map(RuleMatch::decision).max(),空列表即None; matchedRules列出所有前缀命中的规则,matchedPrefix是实际命中的精确前缀(由 src/rule.rs 的PrefixPattern::matches_prefix返回,长度等于 pattern 长度);resolvedProgram仅在绝对可执行路径经 basename 回退命中时出现(Option::is_none时跳过序列化,见 src/rule.rs);- 有效
decision取所有命中判定中最严格的一个(forbidden>prompt>allow)。源码依据:Decision派生Ord且变体顺序即严重程度顺序,src/policy.rs 的Evaluation::from_matches直接对所有命中的 decision 求.max();CLI 输出同理。
justification 字段同样按需序列化,命中时会随 prefixRuleMatch 一起返回,供上层在审批提示或拒绝消息中展示。
六、匹配与求值流程小结,以及预览状态说明
结合上文,一次 codex execpolicy check 的完整流程是:
- 加载:按
--rules顺序读取策略文件,starlark 解析并执行prefix_rule/host_executablebuiltin,累积为Policy; - 加载期校验:对每条规则的
match/not_match示例做正反例校验,失败即拒绝加载并附位置信息; - 求值:先精确匹配首 token,未命中且开启
--resolve-host-executables时走 basename 回退(受host_executable白名单约束); - 输出:序列化全部命中规则与“最严格”的有效 decision 为 JSON。
需要特别注意 README 结尾的提示:execpolicy 命令仍处于 preview 阶段,API 未来可能有不兼容变更,基于它编写自动化时应以当前仓库 codex-rs/execpolicy 的实际实现为准。从源码结构看,语言扩展已在进行中——parser 已内置 network_rule(host=..., protocol=..., decision=...) builtin(支持 http / https / socks5_tcp / socks5_udp 协议,且 deny 等价于 forbidden,见 src/rule.rs 与 tests/basic.rs 中的网络规则测试),后续版本的策略语言可以在此基础上进一步扩展。
参考路径
- codex-rs/execpolicy/README.md:本篇主体文档
- codex-rs/execpolicy/examples/example.codexpolicy:语法示例策略
- codex-rs/execpolicy/src/parser.rs:Starlark 解析与 builtin、示例校验调度
- codex-rs/execpolicy/src/policy.rs:Policy 结构、精确/basename 匹配、Evaluation 求值
- codex-rs/execpolicy/src/rule.rs:PrefixPattern 匹配、RuleMatch 序列化、正/负例校验
- codex-rs/execpolicy/src/execpolicycheck.rs:check 子命令参数与 JSON 输出
- codex-rs/execpolicy/src/decision.rs:三值 Decision 与解析
- codex-rs/execpolicy/tests/basic.rs:解析、匹配、网络规则等测试
- codex-rs/execpolicy-legacy/:旧版规则匹配器
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0629
MiniCPM5-2BMiniCPM5-2B 是一款面向端侧、本地部署和资源受限场景的 2B 稠密 Transformer,能够达到同尺寸开源模型 SOTA 水平。Markdown00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00