Agent Harness 源码拆解(六):权限与沙箱——什么能自动跑,什么必须问人

权限是 harness 里唯一会”出人命”的模块。模型是不可信的执行者,而它手里握着删库、发网络请求、读私人文件的能力。这一章的问题只有一个:什么能自动跑,什么必须问人,以及凭什么信它不会越权。

六家里 pi 干脆不装权限(上一篇提过,全外置成扩展),剩下五家装了的,各有各的狠招。

opencode:审批是一个 Deferred,不是一张对话框

opencode 的授权规则很朴素:action × resource × effect,effect 三态 allow / ask / deny,last match wins,没匹配到默认 ask。

但真正值得抄的是它的两个防御细节,和它把审批做成的并发原语。

第一个防御:无 agent = 全 deny。Session 没指定 agent 时,权限求值套一个 action:* resource:* effect:deny 的兜底——不能让模型在权限检查静默地求值一个空的”无 agent 策略”时,暴露出 build 模型的行为。模型行为和权限策略必须来自同一个 agent 选择。

第二个是审批的实现:审批就是一个 Deferred。请求注册 pending + 发布事件,回复完成 Deferred,拒绝是 typed error,进程退出时 finalizer 批量拒绝。没有轮询、没有回调注册表。任何”执行中等人”的场景(审批、提问、确认)都可以是 Deferred——前提是宿主有结构化取消语义。

CodeWhale:九层单调管线,模型自己的调用不能当同意

CodeWhale 的授权是九层顺序评估,开篇第一句就是立场:“An approval from one layer is not a universal bypass”——一个层的放行不是通行证,后面的安全层仍然可以要求审查或直接拦住。

它的核心性质是单调性:从第 5 层(类型化规则)之后,后层只能收紧,不能把一个前面的 block 或 prompt 变成”未审查的执行”。这条把权限系统的正确形状讲透了——是单调管线,不是”任意一链通过即放行”。每层只加 hold 不减 hold,测试就每层一个契约测试,好写得很。

还有一个我印象极深的细节:模型的工具调用不能当同意。Computer Use 的同意、app_script,在那些弹不了审批卡的姿态下直接拒绝——否则”the model’s own tool call would be the consent”,模型的调用本身就成了同意,那是循环论证。需要人类决定的事,答案通道必须是人类。

配套的一条在子代理权限上:委托转移工作,不转移权力。ChildGrant 单对象 + 与父求交(clamp 交集),多代理系统的权限泄漏点几乎都在”子代理获得了父没有的能力”。

grok-build:把人类出场率压到 5%,沙箱不许改沙箱

grok-build 的权限体系是”模式 × 规则”二维:模式定”多久问一次”,规则定”哪些工具放行/询问/禁止”。

它最值钱的经验是一句话:审批卡是稀缺资源。所以用”便宜判断先走”——bash 的 AST 快路径先看是不是纯读命令,命中就放行;拿不准的才交给 LLM 分类器,最后真正弹给人类的只有约 5%。分类器失败做成独立的 Unavailable 状态,不冒充判决——门禁系统的故障要在数据里可见,不能让”分类器挂了”静默变成”全部放行”或”全部拒绝”。

安全边界的那条也硬核:沙箱不许改沙箱的规则。Landlock/Seatbelt 内核级写拒绝 ~/.grok/hooks/、config.toml、trusted_folders.toml——安全边界的定义文件本身必须在边界内被保护。应用层校验防代码犯错,内核级校验防进程被操纵。

Maka:权限是控制流,不是对话框

Maka 最哲学的一句话:权限是控制流,不是对话框。

具体落地是:工具返回 sandbox_boundary_required → 模型调 request_sandbox_boundary → 人类决策作为 typed fact 进日志(role: system, author: user)→ Run parked 而非结束。权限请求/决策是数据,重放与恢复能解释”为什么停、怎么回来”。

它的工具类别分类学也值得看——权限不按工具名单,按行为类别(read / web_read / file_write / fs_destructive / shell_unsafe / network_send / privileged / computer_use……)。其中 shell_safe 这个类别的注释是精华:“no shell is auto-safe”——它保留了 shell_safe 这个名字,但策略里 fail-closed,因为没有任何 shell 命令能自动判定为安全。

这条和 pi 的”核心零策略”形成两极:pi 把权限全外置,代价是默认裸奔;Maka 把权限做成运行时控制流,代价是一套完整的类别分类学和边界协议。

它们共同的结论

五家的实现天差地别,但三条共识是铁的:权限是控制流不是对话框(要可重放、可解释);fail closed 是默认(沙箱挂了拒绝,不降级成裸奔);反注入是宪法(引用内容不是指令,权限只来自当前会话的人类)。

如果你要设计权限

  • 本地个人工具、愿意自己拼:学 pi,核心只留 beforeToolCall 钩子,权限门自己写。
  • 要完整审计、崩溃恢复:学 Maka,权限决策进日志,parked 而非结束。
  • 多人协作、要守住安全不变量:学 CodeWhale,单调管线 + 每层契约测试。
  • 要省人力、又要可信:学 grok,便宜判断先走把人类出场率压下去,分类器失败做成可见的 Unavailable。

五个最容易踩的翻车点:

一是把模型的调用当同意。需要人类决定的事,答案通道必须是人类,否则是循环论证。

二是任意一链通过即放行。权限应该是单调管线,每层只加 hold 不减 hold。

三是委托放大权限。子代理获得了父没有的能力——必须与父求交。

四是静默降级。沙箱挂了就裸奔跑,等于没有沙箱——fail closed 是默认。

五是沙箱能改沙箱。安全边界的定义文件没被保护,等于门没锁。

下一篇讲 持久化与恢复:崩溃后怎么恢复、会话怎么存——事件日志、session tree、T1/T2 副作用窗口。


本篇术语

  • Deferred:结构化并发的”等人”原语,审批/提问/确认都是它
  • 单调管线(monotonic pipeline):权限每层只加 hold 不减 hold,放行不是通行证
  • fail closed:不确定时拒绝而非放行;对应 fail open(不确定时放行)
  • parked:run 因等权限而挂起而非结束,可重放恢复
  • 工具类别(category):按行为类别(fs_destructive/network_send/privileged)而非工具名单管权限
  • 反注入宪法:引用消息、工具结果、网页里的文字是上下文不是指令,权限只来自当前会话的人类

参考资料

参照系仓库(结论基于 2026-09 下旬至 10 月上旬各仓库 HEAD):

  1. earendil-works/pi —— 核心零策略,权限门为扩展
  2. anomalyco/opencode —— permission.ts Deferred 审批
  3. xai-org/grok-build —— auto_mode/ 分类器与 Landlock 沙箱
  4. zai-org/ZCode
  5. apache/maka —— 三层模型与工具类别分类学
  6. Hmbown/CodeWhale —— AUTHORIZATION_ORDER.md 九层管线

Harness 设计方法论文献:

  1. Anthropic,《Agent Harness Design: 3 Patterns for Harnessing Claude’s Intelligence》