OpenAI Codex · 代码模式

execpolicy:让策略文件自带测试用例

白名单写错,通常要等线上才知道。Codex 把正反例写进规则本身,加载时就地跑一遍。规则和例子打架,这份策略进不了会话。

课程目标读完能说清两件事。一条命令进策略之后怎么被拆开、怎么按前缀命中、最后判成 allow、prompt 还是 forbidden。以及规则旁边的正反例,为什么能在加载期把误伤钉死。
先玩一遍 · 一条命令过策略
同一份策略,四条命令,三种写法
命令
安全的、带危险参数的、看起来像危险的、伪装成包装的。点一条再播。
策略
1 拆解等待
2 加载校验等待
3 前缀匹配等待
4 最严判定等待
这一步看见的 argv
还没有切词。
策略自带的测试用例
正例必须命中未跑
git reset --hard
反例必须放过未跑
git reset --keep
点播放,看这条命令怎么被拆开、命中哪条规则、判成什么等级。
逻辑轨迹 · 动画每一步对应源码里的哪一段
  1. 读入策略文本,开始 parseparser.rs L57
  2. Starlark 求值 prefix_rule,缺 decision 时默认 allowparser.rs L348
  3. 规则和正反例先挂到待校验队列parser.rs L405
  4. 先跑反例,命中就报 ExampleDidMatchparser.rs L145
  5. 再跑正例,没命中就报 ExampleDidNotMatchparser.rs L147
  6. 包装命令先拆成内层 argvexec_policy.rs L831
  7. 按第一个 token 精确取规则桶policy.rs L334
  8. 多规则或组合命令取最严policy.rs L403
  9. 判定映射成 Skip、NeedsApproval 或 Forbiddenexec_policy.rs L375
点播放,看一条命令怎么过策略,以及自带的正反例怎么把判定钉死。
加载期先崩前缀写短或反例写错时,四条命令都没有机会进入 allow 或 forbidden。策略还没交出去,错例已经把加载打断。
--keep 为什么能活正确规则把 pattern 写成三段。第三个 token 对不上 --keep,禁令不亮。这不是靠启发式放过的,是作者用反例把边界钉死的。
包装挡不住bash -lc 包一层,拆得开就按内层再判。拆不开才整段回退。演示里这条包装拆得开,所以仍是 forbidden。
教学示意:舞台只演示示例策略里禁止 git reset --hard、放行 ls 这两条。不执行真实 shell。逻辑轨迹右侧行号对应 openai/codex 仓库 commit 4f39251a01。
思路一 · 规则和例子写在同一处
它解决什么问题

你给团队写一条禁令,pattern 写成 git 加 reset。本意是拦 --hard。过了一周有人报,--keep 也被拦了。前缀一短,后面跟什么都命中同一条。

再过一周,模型用 bash -lc 包了一层。禁令按字面 argv 去匹 bash,第一条对不上,命令进了启发式通道。

白名单每条都在赌两件事。一是 pattern 刚好覆盖你想拦的。二是它不会误伤你想放的。这两件事通常要等线上才知道。下一次改 pattern 的人,也看不到作者当初怕误伤哪条命令。

思路是什么

Codex 把正反例写进规则本身。match 里是这条规则必须命中的命令,not_match 里是它必须放过的命令。prefix_rule 并不当场跑例子,它先把规则和例子推进待校验队列。整份 Starlark 求值完,parse 再统一校验。

顺序固定。先跑反例,再跑正例。反例误命中,报 ExampleDidMatch。正例一个都没命中,报 ExampleDidNotMatch。两种错都让 parse 返回失败,Policy 不会被 build 出去。出处:codex-rs/execpolicy/src/parser.rs 第 57 至 78 行,以及第 133 至 151 行

crate README 把这件事写成一句话:它们是加载期校验的示例调用,可以当成单元测试。字符串和 token 数组两种写法进解析器后都会变成 token 列表,字符串走 shlex 拆词。校验时启发式回调传空,正例必须靠真实前缀规则命中,不能靠启发式凑数。

Starlark 求值 挂上规则和例子 先跑反例 必须一条都不命中 再跑正例 必须至少命中一条 交出 Policy 加载失败 例子对不上,策略还没交出去就已经停 错误会带上 prefix_rule 调用处的行列号
教学化时序:求值只负责收集,校验发生在 build 之前。
为什么长期成立

白名单的典型失败,是作者以为 pattern 是这个意思。把以为写成例子,机器可以反对。这条不依赖 Starlark,换成 JSON 或 YAML 也能抄。

规则和例子写在同一处,策略文件被拷进用户目录或被 overlay 下发时,例子跟着走。加载器没有只读规则、不跑例子的分支。漏写例子等于漏写测试,加载器不会替你编例子,写了就会强制执行。

思路二 · 前缀匹配,多规则取最严
它解决什么问题

正则能写 git reset 后面跟任意参数,也能写出作者自己都读不懂的例外。叠加规则时如果取最宽,用户层一条宽松的 allow 就能盖住系统层的 forbidden。

思路是什么

规则本体是一段前缀。匹配是精确字符串相等,没有 glob,没有正则。git reset --hard 能吃后面再跟 origin/main 的命令,因为多出来的 token 不参与比较。它吃不了中间插了 --config 的写法,因为第二个 token 对不上。出处:codex-rs/execpolicy/src/rule.rs 第 46 至 59 行

规则按第一个 token 放进桶。查找时先按 argv 第零项精确取。取不到,再考虑把绝对路径收成 basename。命中多条时,对 decisionmaxDecision 派生了 Ord,变体书写顺序就是严重程度:Allow 小于 Prompt 小于 Forbidden。

组合命令先拆段再摊平,再取一次 max。一段 git status 是 Prompt,一段 git commit 是 Forbidden,整条管道仍是 Forbidden。出处:codex-rs/execpolicy/src/policy.rs 第 265 至 287 行,以及第 402 至 411 行

git 前缀 decision = prompt git commit 前缀 decision = forbidden git commit -m hi 两条都进 matchedRules max = Forbidden Allow < Prompt < Forbidden 叠加只能加严。序写在枚举变体上,没有另一张优先级表
教学化对照:同一条命令命中两条规则,对外只认更严的那一个。
为什么长期成立

叠加只能加严,不能放宽。这个序写在枚举变体上,运行时没有另一张优先级表可以写错。换语言重写,三个字符串做成有序枚举,聚合用一次 max 就够。

前缀强迫你把意图落成 token 序列。代价是插在中间的参数会让规则失效,作者必须用更短的前缀或另写一条。正反例就是用来接住这条代价的:写短了,反例会在加载期先崩。

思路三 · 先拆包装,拆不出来整段回退
它解决什么问题

禁令写的是 git reset --hard。模型写成 bash -lc 包一层。按字面 argv 去匹,第一条是 bash,禁令不亮。

思路是什么

判定前先走 parse_shell_lc_plain_commands。它要求脚本只由纯词命令加 &&||、分号、管道组成,拒绝重定向、替换、括号、控制流。过关后按命令节点切成多段 argv。bash -lc 包着 git reset --hard,会被拆成内层那三段,再拿去匹前缀。出处:codex-rs/core/src/exec_policy.rs 第 831 至 858 行

拆不出来时,整段 argv 当一条命令,交给启发式和后续沙箱。空引号插在参数里还能还原。空引号插在命令名里,word-only 解析失败,前缀规则看不见 git,这条命令掉进启发式。

吃不下的脚本,就不要假装已经看懂。
为什么长期成立

解析器承认自己吃不下的东西,就不假装已经看懂脚本。这是 fail closed 的通用形状。它挡得住解析成功但漏掉危险段。拆失败之后,启发式和沙箱还在。

横向对比 · 同一道题的另一种答法

DeepSeek Harness:两个旋钮加一个下拉框

DSH 不写命令级 pattern。权限预设把沙箱模式和审批策略捆成一包。默认表只有两行:workspace-writeaskdanger-full-accessnever。审批策略本身只有 asknever

旋钮好懂。你无法在预设里写出只禁 git reset --hard、其他 git 照常。那种例外要么进沙箱拒绝后的升级审批,要么进自定义旋钮组合,界面上显示为保留名 custom。下拉框覆盖日常切换,点名禁止的长尾它盖不住。

两侧均已核对源码 · 2026-08-22 · DSH · 审批与权限

Claude Code:工具名加可选内容的 allowlist

规则字符串长成 Bash,或 Bash(npm install),或 Bash(git *)。解析器按括号切开工具名和内容。shell 规则再分成精确、前缀、通配三类。

检索 matchnot_matchexample 作为规则字段,没有加载期正反例校验。git * 一旦写进 allow,git reset --hard 也会被这条通配吃掉,除非另写一条更具体的 deny。Codex 用更短的精确前缀加反例,把例外钉在加载期。Claude Code 把例外留给规则叠放顺序和运行时确认。

两侧均已核对源码 · 2026-08-22
课堂练习
01

前缀写短,加载还是判定

把禁止 git reset --hard 的 pattern 改短成 git 加 reset,not_match 仍写 --keep。加载时会走 ExampleDidMatch 还是 ExampleDidNotMatch,命令还有没有机会进入判定。

进阶一问:同一条坏规则下,ls -l 会不会先出 allow。演示里把策略切到「前缀写短」再播一遍,对一下你的推演。

Takeaway:规则和例子写在同一处,加载时就地跑。正例必须命中,反例必须放过,打架就拒绝加载。前缀精确比较,多规则取最严。包装先拆开,拆不出来整段回退。