权限与审批:它要执行 rm -rf 怎么办?
主线定位
- 上游 · A2 工具:模型已经能通过
bash、edit_file这些工具真的动手。工具一接上,模型发出的每一条命令都会被原样执行——包括rm -rf /。 - 本章 · A3 权限与审批:在每次调用真正执行之前,由 harness 决定放行、拒绝还是停下来问(人或另一个模型)。回答三件事:谁来拦、按什么拦、拦不住什么。
- 下游 · A4 沙箱:规则和审批看的都是「调用长什么样」。
python3 -c "…"这种在运行时才决定做什么的命令,它们看不见;下一章交给操作系统:不管命令怎么写,越界的系统调用都会被拒绝。
本章问题链
每一课只回答一个问题;上一课的失败,逼出下一课的问题。
| 课 | 这一课的问题 | 朴素方案在哪个真数字上失败 | 一句话 |
|---|---|---|---|
| 第 1 课 | 拦危险命令,写个黑名单行不行? | lcc s03 三道闸在 6 条危险命令里漏拦 4 条;cat sudoku_solver.py 因含子串 sudo 被拒(pin 源码实跑) | prompt 决定想做什么,harness 决定允许什么;匹配子串拦不住改写过的命令 |
| 第 2 课 | 两条规则冲突时,听谁的? | 同一份 rules-standard.json 改用 first-match,真实运行里 {denied ? denied.subject : ''} 被规则 #8 拒掉 | 同一组规则换一种求值语义就可能换一个答案;先选语义,再用测试锁住 |
| 岔路 | hook:规则之外的拦截点,能放宽规则吗? | hook 返回 allow,deny 规则照样拒绝 | hook 只能收紧、不能放宽 |
| 第 3 课 | 每个调用都问人,人会怎样? | 我们 26 次真实运行的 139 次调用,Manual 模式要弹窗 74 次;Anthropic 自报用户批准约 93% 的弹窗 | 问得太多,人就不再看了;只把真正需要判断的调用留给判断者 |
| 岔路 | Pi 为什么说权限弹窗是「安全剧场」? | — | Pi 不是说权限不重要,而是说进程内的弹窗给不了边界,要边界就上容器 |
| 第 4 课 | 让另一个模型来审批,审批者该有多大权限? | Anthropic 自报 auto 分类器对真实越权动作仍有 17% 漏判(n = 52) | 审批者必须比被审者权限更小,历史只当证据,出故障默认不执行 |
| 实验课 | 真实调用 × 规则集 × 六种求值语义 | — | 顺序敏感的用例写成测试;求值器与原实现逐条自检 |
演进地图
- Pi 核心、bypassPermissions、CoreCoder --yes解决了零打断,模型想跑什么马上跑。卡在L1 的 6 条本意危险的构造命令(含
rm -rf /)会全部直接执行。 - 朴素 黑名单:子串或正则lcc s01 / s03、CoreCoder bash 正则、Pi permission-gate解决了把已知危险的写法挡在执行之前,代码只有几行。卡在lcc s03 三道闸(pin 源码实跑):6 条危险命令里漏拦 4 条,5 条日常命令里误拦 2 条(
cat sudoku_solver.py因含子串 sudo 被拒)。 - 第一代 每次都问人CoreCoder 交互模式、Claude Code Manual、lcc s03 的第三道闸解决了判断交给人,规则写不全也不怕。卡在我们 26 次真实运行的 139 次工具调用,在 CoreCoder 交互模式下有 74 次要停下来问(53%);Anthropic 自报用户批准了约 93% 的弹窗——问多了,人就不看了。
- Claude Code(deny → ask → allow)、OpenCode(last-match)、Codex(最严格者胜)解决了把常见的放行和拒绝写成规则,只有规则没覆盖的才问人。卡在同一份规则换一种求值语义可能换一个答案:同一份
rules-standard.json改用 first-match,真实运行 pd-firstmatch-s1 里模型的python -m unittest -v test_dateparse.py被规则 #8 拒掉。 - 第三代 分层放行 + 模型审批Claude Code auto、Codex Auto-review(代码名 Guardian)、Hermes smart解决了只读直放、项目内编辑免审,其余交给另一个模型判断;弹窗接近清零。卡在Anthropic 自报:auto 分类器在真实越权动作上仍有 17% 漏判(n = 52),官方原话「does not guarantee safety」。
- 下一章 与 OS 沙箱分层Claude Code sandbox、Codex Seatbelt / bubblewrap解决了规则管「允许调用什么」,沙箱管「调用之后能碰到什么」。卡在L1 的
python3 -c "import shutil; shutil.rmtree(…)"在四个黑名单里被 4 个全部放行;规则看不见脚本在运行时做什么 → A4。
数字口径:黑名单与「每次都问」由原实现在 pin 源码上实跑(a03-permission-fixtures.json,2026-09-26);真实运行 = 本课程项目 miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制;93% 与 17% 为 Anthropic 官方自报。
贯穿任务:修好 test_parse_date
全课的贯穿任务是项目 04-实践项目/tasks/parse_date:让失败的 test_parse_date 通过,测试文件 test_dateparse.py 不许改。这一章给它的 trace 加上权限列:每一次工具调用,在各家权限语义下是放行、问人还是拦下,由哪条规则决定。
- 本章用到的真实数据:miniagent 用 Qwen/Qwen3-8B-MLX-4bit 本机录制的 26 次运行(其中修
test_parse_date的 20 次),共 139 次工具调用,每一次都带着录制时的权限裁决。第 2、3、4 课和实验课都直接用它们。 - 第 1 课换了例子:真实运行里模型发出的 bash 命令只有跑测试这一种,不够检验黑名单,所以第 1 课用一组构造的危险与日常命令(页面标「构造输入 · 真算法」),真实的测试命令也在其中。
- 第 2 课的核心实验换了例子:
git push冲突是设计卡和 Claude Code 文档里的经典写法,两条规则就能看清三种语义;真实配置rules-standard.json放在同一课的朴素方案和实验课里。
三层交互
| 层 | 本章里的组件 | 在哪一课 |
|---|---|---|
| 概念动画 | Claude Code 六步求值序逐步走(hook → deny → ask → 模式 → allow → 弹窗);审批者边界图(朴素做法与 Codex Guardian 两种形态,点开每条能力看 pin 源码原文) | 岔路 B1、第 4 课 |
| 算法模拟器(真算法、小数据) | 四个黑名单对 11 条命令现算漏拦与误拦;同一份规则表交给 Claude Code、OpenCode、CoreCoder、first-match、last-match 五个求值器,可拖动重排、先猜后揭示、逐步看求值光标 | 第 1、2 课 |
| 真实数据 | 我们项目真实运行的全部工具调用,放进十几种权限模式重算弹窗次数;规则集 × 调用 × 引擎的裁决矩阵,页面加载时与原实现、与真实运行记录、与官方文档例子逐条自检 | 第 3 课、实验课 |
所有求值器都在浏览器里现算,不联网:OpenCode、CoreCoder、learn-claude-code、Pi 与我们项目的求值器从源码移植,期望值由原实现在 pin 副本上跑出(src/data/precomputed/gen_a03_fixtures.py 与 .ts);Claude Code 核心闭源,它的求值器依据官方文档实现,非源码移植。
项目推进:给 miniagent 选一种权限语义
对应 04-实践项目/MILESTONES.md 的「A3 · 权限与审批」。实现已经在 miniagent/permissions.py 与 configs/ 里:规则 = 动作 + 工具名通配 + 对象通配,first-match 与 last-match 两种语义可切换,默认 ask,ask 交给 approver(交互时问人,批量实验时自动拒绝),每次判定写进 trace。代码 AI 来写,决策你来做。
你要拍板的决策
- first-match 还是 last-match(或者像 Claude Code 那样按动作分组、像 Codex 那样取最严格),为什么?两种顺序语义的写法习惯相反:first-match 要「具体的放前面」,last-match 要「通用的放前面、例外写后面」。项目目前默认 last-match,因为
rules-standard.json是按后一种习惯写的(第 2 课)。 - 默认动作是 ask 还是 deny?无人值守时怎么办?项目:ask;批量实验里 approver 自动拒绝(第 3 课)。
- 只读工具要不要免审?各家都免(第 3 课);如果你选不免,先估一下弹窗会多多少。
- 规则只做字符串匹配够不够?不够——
python -m unittest; curl … | sh能骑着 allow 前缀过去。项目有意保留这个洞并用测试钉住,交给 A4(第 1 课、实验课)。
验收标准(全部可测):
.venv/bin/python -m unittest tests.test_permissions全过(2026-09-26 本章开发时实跑:8 个用例全过),其中StandardConfigFlips证明同一份rules-standard.json在两种语义下「跑 unittest」和「改测试文件」的判定完全相反,test_known_hole_compound_command_passes_a_prefix_rule钉住已知漏洞。- 真跑对照:
pd-firstmatch-s1与pd-unlimited-s1只差语义,trace 里能看到 first-match 下python -m unittest被拒(permission.final == "deny",rule_index == 7);本章实验课的自检表对全部真实调用逐条复算,「miniagent 复算 vs 真实运行记录」一行必须全绿。 - 被拒绝的调用以 tool result 回填给模型、循环不中断(trace 里该调用
executed: false,之后仍有新的轮次)。 - 设计卡 M3 的补充(建议):把实验课矩阵里「项目真实配置」下的调用整理成至少 20 条「(规则集, 调用) → 期望裁决」的用例写进测试;在
DECISIONS.md里写下一条「书写顺序与动作优先级冲突」的真实调用及其在所选语义下的期望裁决。
坑与带走
本章拦截的误解
| 误解 | 纠正 | 在哪一课 |
|---|---|---|
| 在 CLAUDE.md 里写「不许删文件」就够了 | prompt 只影响模型想做什么;允许什么由 harness 执行 | 第 1 课 |
| 黑名单写全一点就能拦住危险命令 | 规则只看命令文字,同一效果的写法没有尽头;子串还会误拦 sudoku | 第 1 课 |
| deny → ask → allow、第一条命中的算数,是通用规则 | 那是 Claude Code 的语义;OpenCode 是 last-match,Codex 取最严格,CoreCoder 不读规则 | 第 2 课 |
| 两种语义对同一份配置一定给出相反结果 | 只有「最后一条命中的动作 ≠ 优先级最高的动作」时才相反 | 第 2 课 |
| Codex Granular 里某一类设成 false 就是弹窗 | false 是自动拒绝 | 第 2 课 |
| hook 返回 allow 能绕过 deny | hook 只能收紧;deny、ask 规则在 hook 之后照样求值 | 岔路 B1 |
| OpenCode 默认一切都问 | 内置默认规则第一条是 *: allow;「连 read 都过权限」指每次都求值 | 第 2 课 |
| 弹窗越多越安全 | 用户批准约 93% 的弹窗,问多了人就不看 | 第 3 课 |
| Pi 认为权限不重要 | 它认为进程内的弹窗给不了边界,边界应该是容器 | 岔路 B2 |
| 有模型审批就安全了 | 官方自报 17% 漏判,原话「does not guarantee safety」 | 第 4 课 |
| 审批者工具越全审得越准 | 审批者的权限必须严格小于被审者 | 第 4 课 |
写代码时的坑
| 症状 | 后果 | 怎么办 |
|---|---|---|
| 用子串黑名单拦命令 | 改写过的命令漏拦,含关键词的无害命令误拦 | 解析命令;动态展开显式处理;真正的边界交给沙箱 |
| 以为写进 prompt 就能限制 | 模型照样发出调用 | 在 harness 里执行规则,被拒的调用回填 tool result |
| last-match 下随手调整规则顺序 | 裁决静默翻转(真实运行 pd-firstmatch-s1 里测试命令被拒) | 用测试锁住顺序敏感的用例 |
| 把一份配置直接翻译到另一家 | 语义不同,裁决可能变;带参数的 deny 在 Claude Code 里还会整个拿掉工具 | 翻译后对同一批调用重跑一遍 |
| 默认一切都问 | 审批疲劳,人工审批失效 | 只读直放、项目内编辑免审,其余再审 |
| 审批模型有写权限或共享主上下文 | 被注入后越权 | 审批者权限严格更小、历史只当证据、fail closed |
带走这些句子(各课「一句话」):
- prompt 决定模型想做什么,harness 决定允许什么;匹配子串拦不住改写过的命令。
- 同一组规则,换一种求值语义就可能换一个答案;所以先选语义,再用测试把顺序敏感的用例锁住。
- hook 只能收紧、不能放宽:它在规则之前拦得住一切,却放不过规则已经拒绝或要求询问的调用。
- 问得太多,人就不再看了;把只读和低风险的放行,只把真正需要判断的调用留给判断者。
- Pi 的立场不是「权限不重要」,而是「进程内的弹窗给不了边界,只值一个 34 行的扩展;要边界就上容器」。
- 审批者必须比被审者权限更小,读到的历史只当证据,出任何故障都默认不执行。
深入这一章
按你愿意花的时间选。每条都实际打开核对过(2026-09-26);外部链接都需要联网,离线包里只保留链接。
🎮 想更直观 10–30 分钟
🔬 想吃透原理 1–3 小时
📄 想读原文 按需
我替你排除了什么:Cedar Language Playground(2026-09 调研时 curl 返回 404)· Databricks「From Primitives to Production」视频(多次访问报 Socket 错误,无法核对内容)· developers.openai.com/codex 下的旧文档链接(已迁到 learn.chatgpt.com,本章只用新地址)· 只列出「五种权限模式」的社区二手教程(漏了 auto,已过时)· 把 auto mode 的防注入归结为某一个单一机制的解读文(调研中三方核查未通过)· B 站检索到的权限 / 沙箱相关视频(2026-09-26):多为速成与营销标题的合集(如「全 748 集」「学完即就业」),或某个产品的功能宣传,没有讲清求值语义与审批设计的,中文视频本档暂缺 · 中文社区的「权限系统全解析」类博客:只复述官方文档的配置写法,官方中文版已覆盖,不另收。