execpolicy:让策略文件自带测试用例
白名单写错,通常要等线上才知道。Codex 把正反例写进规则本身,加载时就地跑一遍。规则和例子打架,这份策略进不了会话。
这一步看见的 argv
策略自带的测试用例
- 读入策略文本,开始 parseparser.rs L57
- Starlark 求值 prefix_rule,缺 decision 时默认 allowparser.rs L348
- 规则和正反例先挂到待校验队列parser.rs L405
- 先跑反例,命中就报 ExampleDidMatchparser.rs L145
- 再跑正例,没命中就报 ExampleDidNotMatchparser.rs L147
- 包装命令先拆成内层 argvexec_policy.rs L831
- 按第一个 token 精确取规则桶policy.rs L334
- 多规则或组合命令取最严policy.rs L403
- 判定映射成 Skip、NeedsApproval 或 Forbiddenexec_policy.rs L375
你给团队写一条禁令,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 拆词。校验时启发式回调传空,正例必须靠真实前缀规则命中,不能靠启发式凑数。
白名单的典型失败,是作者以为 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 行
叠加只能加严,不能放宽。这个序写在枚举变体上,运行时没有另一张优先级表可以写错。换语言重写,三个字符串做成有序枚举,聚合用一次 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。下拉框覆盖日常切换,点名禁止的长尾它盖不住。
Claude Code:工具名加可选内容的 allowlist
规则字符串长成 Bash,或 Bash(npm install),或 Bash(git *)。解析器按括号切开工具名和内容。shell 规则再分成精确、前缀、通配三类。
检索 match、not_match、example 作为规则字段,没有加载期正反例校验。git * 一旦写进 allow,git reset --hard 也会被这条通配吃掉,除非另写一条更具体的 deny。Codex 用更短的精确前缀加反例,把例外钉在加载期。Claude Code 把例外留给规则叠放顺序和运行时确认。
前缀写短,加载还是判定
把禁止 git reset --hard 的 pattern 改短成 git 加 reset,not_match 仍写 --keep。加载时会走 ExampleDidMatch 还是 ExampleDidNotMatch,命令还有没有机会进入判定。
进阶一问:同一条坏规则下,ls -l 会不会先出 allow。演示里把策略切到「前缀写短」再播一遍,对一下你的推演。