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。演示裏把策略切到「前綴寫短」再播一遍,對一下你的推演。