OpenAI Codex · 代码模式 execpolicy:让策略文件自带测试用例 白名单写错,通常要等线上才知道。Codex 把正反例写进规则本身,加载时就地跑一遍。规则和例子打架,这份策略进不了会话。 一句话速览 正反例写进规则本身,加载期就跑一遍;前缀精确匹配,多规则取最严
课程目标 读完能说清两件事。一条命令进策略之后怎么被拆开、怎么按前缀命中、最后判成 allow、prompt 还是 forbidden。以及规则旁边的正反例,为什么能在加载期把误伤钉死。
先玩一遍 · 一条命令过策略
同一份策略,四条命令,三种写法
播放 单步 重置
命令
ls -l git reset --hard git reset --keep bash -lc 包装
安全的、带危险参数的、看起来像危险的、伪装成包装的。点一条再播。
策略
正确规则 前缀写短 反例写错
判定
1 拆解 等待 2 加载校验 等待 3 前缀匹配 等待 4 最严判定 等待
这一步看见的 argv
还没有切词。
策略自带的测试用例
正例必须命中 未跑 git reset --hard
反例必须放过 未跑 git reset --keep
点播放,看这条命令怎么被拆开、命中哪条规则、判成什么等级。
逻辑轨迹 · 动画每一步对应源码里的哪一段
读入策略文本,开始 parse parser.rs L57 Starlark 求值 prefix_rule,缺 decision 时默认 allow parser.rs L348 规则和正反例先挂到待校验队列 parser.rs L405 先跑反例,命中就报 ExampleDidMatch parser.rs L145 再跑正例,没命中就报 ExampleDidNotMatch parser.rs L147 包装命令先拆成内层 argv exec_policy.rs L831 按第一个 token 精确取规则桶 policy.rs L334 多规则或组合命令取最严 policy.rs L403 判定映射成 Skip、NeedsApproval 或 Forbidden exec_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。命中多条时,对 decision 做 max 。 Decision 派生了 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-write 配 ask , danger-full-access 配 never 。审批策略本身只有 ask 和 never 。 旋钮好懂。你无法在预设里写出只禁 git reset --hard 、其他 git 照常。那种例外要么进沙箱拒绝后的升级审批,要么进自定义旋钮组合,界面上显示为保留名 custom 。下拉框覆盖日常切换,点名禁止的长尾它盖不住。 两侧均已核对源码 · 2026-08-22 · DSH · 审批与权限
Claude Code:工具名加可选内容的 allowlist 规则字符串长成 Bash ,或 Bash(npm install) ,或 Bash(git *) 。解析器按括号切开工具名和内容。shell 规则再分成精确、前缀、通配三类。 检索 match 、 not_match 、 example 作为规则字段,没有加载期正反例校验。 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: 规则和例子写在同一处,加载时就地跑。正例必须命中,反例必须放过,打架就拒绝加载。前缀精确比较,多规则取最严。包装先拆开,拆不出来整段回退。