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:規則和例子寫在同一處,載入時就地跑。正例必須命中,反例必須放過,打架就拒絕載入。前綴精確比較,多規則取最嚴。包裝先拆開,拆不出來整段回退。