权限门控模式
问题
如果没有统一的权限门控,每次工具调用要么静默放行(产生安全风险),要么盲目拦截(产生使用摩擦)。核心困难在于,权限决策依赖于随执行环境变化的上下文:交互式会话需要人在回路中,无头 Agent 必须自动决策,而 swarm worker 必须委派给协调者。将这些检查分散在各个工具内部会导致逻辑重复、中止信号到达时行为不一致,以及无法干净地记录所有路径上的审计决策。
这些问题出现在任何支持工具使用的 Agent 运行时中——并非特定于某个具体实现。
黄金法则
每次工具调用都通过同一个门控
将每次工具调用路由到一个单一的权限检查点,在执行开始之前进行评估。门控评估规则并返回三种行为之一:允许、拒绝或询问。任何工具都不应自行绕过授权;门控是唯一权威。这使审计变得简单——每个决策都流经一个咽喉——并防止个别工具静默跳过检查。
三种行为,不多不少
权限词汇恰好三个词。"允许"意味着工具立即运行。"拒绝"意味着工具被拒绝,Agent 收到拒绝原因。"询问"意味着运行时无法独自决定,必须咨询外部权威——人类、协调者或升级 hook。每种行为都携带区分性原因,使下游日志记录和显示无需重新解析决策。
分层规则按严格优先级顺序评估
规则评估遵循固定的优先级链。显式拒绝规则最先触发——它们是最快且最严格的。显式询问规则其次触发。然后运行工具特定的内容检查(用于需要超出通用系统逻辑的工具,如基于路径的访问控制)。工具特定检查之后,不可绕过的安全检查触发。只有在所有这些之后,系统才会咨询模式级覆盖(如"自动批准一切")或宽泛的允许规则。如果没有匹配,默认是询问。这种排序保证安全关键约束永远不会被宽松模式静默覆盖。
某些"询问"结果不可绕过
某些条件必须始终呈现为"询问",无论运行时模式多么宽松。本质上需要人类在场的工具、明确要求确认的内容级规则,以及对受保护路径(如版本控制内部或 Agent 配置目录)的安全检查——这些都产生跳过自动批准捷径的询问结果。没有这种豁免,一个配置错误的绕过模式可能静默修改受保护状态。
规则评估与 UX 分发分离
关于做什么的决策(允许、拒绝或询问)在专用的规则评估器中,独立于 UX 分发层。关于如何呈现"询问"结果的决策——显示对话框、向协调者发送消息、转发给 swarm leader——在环境特定的处理器中。评估器本身可能在评估的副作用中携带状态(拒绝跟踪、模式转换),但关键不变式是:添加新的执行环境只需要一个新处理器和一个分发分支,而不需要修改规则逻辑。
通过原子声明实现竞态安全的决议
当多个并发路径可以解决一个"询问"——用户输入、后台 hook 和自动分类器都在竞争——第一个完成的必须干净地胜出。原子声明机制在任何异步工作进行之前将权限标记为已解决,防止第二个异步赢家在第一个已经提交后调用解析器。没有这个机制,两条路径都会认为自己赢了,产生重复或矛盾的决议。
权限上下文是冻结的服务对象
将所有有状态操作——中止信号检查、队列管理、审计日志记录和规则持久化——捆绑到一个上下文对象中,然后冻结它。处理器接收这个对象作为其权限状态的唯一接口。冻结防止跨处理器边界的意外修改,而将操作集中到对象上防止处理器重复连接代码。这不是调用者可以修补的可变选项包;它是一个密封的服务契约。
规则来源有序且可组合
权限规则来自多个来源:用户级设置、项目级设置、本地覆盖、功能标志、策略指令、CLI 参数、命令级作用域和会话级授权。每个来源独立贡献其拒绝、询问和允许列表。评估时,所有来源被合并,严格优先级链在合并集上生效。持久化定向到适当的来源——"为这个项目记住"写入项目设置,"为这个会话记住"写入会话状态——使规则可以组合而不会互相覆盖。
适用场景
- 你的 Agent 运行时执行可修改文件、运行命令或访问外部服务的工具。
- 你需要在交互式、无头和多 Agent 环境中保持一致的权限执行。
- 你需要审计跟踪,显示谁授权了每次工具调用及原因。
- 你的工具有异质的风险概况——有些可以安全地自动批准,有些必须始终要求确认。
- 你支持多个配置作用域(用户、项目、会话),它们需要组合而不冲突。
- 你需要在不重写权限逻辑的情况下添加新的执行环境。
权衡
| 决策 | 收益 | 代价 |
|---|---|---|
| 所有工具的单一门控 | 一个审计咽喉,一致的执行 | 每次工具调用都要付出门控延迟 |
| 三行为词汇 | 简单的心智模型,穷举的情况处理 | "询问"是一个宽泛的桶——不同环境处理方式不同 |
| 严格的优先级排序 | 拒绝总是赢,安全检查不可绕过 | 规则作者必须理解评估顺序才能预测结果 |
| 不可绕过的询问结果 | 受保护路径无论模式如何都保持受保护 | 即使在自动批准模式下,某些工具也比预期慢 |
| 评估与分发分离 | 新环境只需一个新处理器 | 需要理解两层而非一层 |
| 决议的原子声明 | 无重复或矛盾的决议 | 处理器编写稍复杂 |
| 冻结的上下文对象 | 处理器无法损坏共享状态 | 调用处无法临时扩展——所有操作必须预先定义 |
| 多来源规则组合 | 细粒度作用域(用户、项目、会话) | 合并逻辑更复杂;调试"哪个来源赢了?"需要追踪 |
实现模式
- 定义一个权限决策类型,恰好包含三种行为:允许、拒绝和询问。为每种行为附加区分性原因,使日志记录和显示消息永远不需要重新推导原因。
- 仅在工具需要超出通用规则系统的逻辑时才实现工具特定的权限检查(基于路径的访问控制、配额执行等)。默认应完全委托给基于规则的系统。仅在明确违规时返回拒绝;不确定的情况返回询问。
- 将任何无法在没有人类在场的情况下运行的工具标记为需要用户交互。无论绕过模式如何,门控始终将这些呈现为询问。
- 用独特的原因类型标记安全关键的询问结果。门控将这些视为不可绕过并跳过任何自动批准捷径。
- 将权限上下文构建为密封的服务对象,捆绑中止检查、队列操作、审计日志记录和规则持久化。在创建时冻结它。
- 在任何可由多个并发路径解析的 promise 上使用原子声明守卫。在异步回调内的任何异步工作之前进行声明。
- 在发送出站请求之前注册远程或 swarm 回调,以消除响应者在回调注册之前就回复的竞态窗口。
- 在决议点而非个别调用处记录每个决策——接受、拒绝、来源、是否被持久化。将此集中到上下文对象上可防止添加新决议路径时遗漏日志。
- 在单一操作中通过内存状态和持久化存储持久化权限更新。返回是否有任何更新是持久的,以便审计日志可以区分临时和永久授权。
- 在模式检查步骤而非函数入口重新读取运行时模式。如果模式在规则评估和模式捷径之间发生变化,缓存的值会应用过时的权限。
- 在"不要询问"模式下将询问转换为拒绝时,在内部规则链完成后执行转换。这确保不可绕过的检查始终先产生询问,然后在外层被转换为拒绝。合并这些步骤有压制不可绕过路径的风险。
踩坑指南
不要在整个评估过程中缓存运行时模式。 模式检查步骤必须在评估时重新读取当前模式。如果你在函数入口快照模式,在规则评估和绕过捷径之间发生的模式切换将不可见,你会应用过时的权限。
不要在规则评估器内运行 hook。 hook 执行属于环境特定的处理器。如果你将其移入评估器,永远不会到达处理器的无头 Agent 会静默跳过 hook。无头环境需要单独的 hook 执行路径,正是因为它们不经过交互式处理器。
规则的允许结果仍需输入规范化。 即使门控通过绕过捷径或宽泛允许规则提前解决,工具的输入仍可能需要清洗。内容特定的允许规则(如 shell 命令的前缀匹配规则)可能返回与原始调用不同的规范化输入。在传递给执行层之前,始终应用输入规范化。
不要将内部规则链与外部模式转换合并。 内部链产生不可绕过的询问结果。外层在限制模式下将询问转换为拒绝。如果你将这些扁平化为一个函数,不可绕过的结果可能无法正确触发,因为拒绝转换可能在它们被标记为不可绕过之前截断它们。
在交互式处理器中遵循交互信号前添加宽限期。 如果没有短暂延迟(几百毫秒量级),偶然的按键——例如提交原始 prompt 的回车——可能在运行中的分类器有时间自动批准之前就取消它。宽限期吸收了在权限对话框实际可见之前到达的输入。
冻结上下文对象不能替代原子声明。 冻结防止上下文字段的外部修改,但对决议 promise 上的异步竞态条件无能为力。两种机制都是必需的:冻结用于封装,原子声明用于决议安全。
在分布式环境中,发送出站请求前注册回调。 在 swarm 或协调者拓扑中,响应者可能在本地处理器注册回调之前就回复。先注册可消除这个窗口。
Claude Code 实证
Claude Code 的权限系统是这些原则的生产实现,处理交互式终端、无头 CI Agent 和多 Agent swarm 拓扑中的工具授权。
单一门控,三种行为。 Claude Code 中每次工具调用都在执行前通过一个顶层权限函数。该函数返回允许、拒绝或询问,每个都携带驱动审计日志和用户可见解释消息的区分性原因。没有工具绕过此门控。
严格的分层评估。 规则评估器遵循固定序列:显式拒绝规则、显式询问规则、工具特定内容检查、不可绕过的安全检查(针对 .git/ 和 .claude/ 等路径)、模式级捷径、宽泛允许规则,以及默认回退到询问。排序是刻意的——安全检查在模式捷径之前触发,因此即使完全绕过模式也无法静默批准对受保护目录的写入。
不可绕过的安全检查。 三类询问结果完全跳过自动批准捷径:声明需要用户交互的工具、明确要求确认的内容级询问规则,以及对受保护路径的安全检查。这一设计源于一个观察:否则一个配置错误的"全部批准"标志可能允许静默修改版本控制状态或 Agent 配置文件。
冻结的权限上下文。 上下文对象在创建时冻结。所有处理器操作——队列推入和移除、权限持久化、中止信号检查、审计日志记录——都是这个密封对象上的方法。这防止不同执行环境中的处理器意外修改彼此的状态,这在多处理器架构早期开发中是一个反复出现的缺陷来源。
决议竞态的原子声明。 交互式处理器竞争三个并发路径:来自确认对话框的用户输入、后台权限 hook 和自动分类器。声明机制在这些路径中的任何一个进行异步工作之前,原子性地将权限标记为已解决。这一设计是在观察到没有它时,分类器和用户都可能"赢得"竞争并产生重复的决议回调后引入的。
三路处理器分发。 规则评估之后,系统根据执行环境将询问结果分发到三个处理器之一:协调者处理器在显示对话框之前按顺序运行 hook 和分类器,swarm worker 处理器通过邮箱将决策转发给 leader,或默认的交互式处理器推送确认对话框并让用户输入与自动检查竞争。添加新的执行环境只需要一个新处理器和一个分发分支——规则评估器不需要更改。
可组合的多来源规则。 权限规则来自八个不同的来源,涵盖用户设置、项目设置、本地覆盖、功能标志、策略指令、CLI 参数、命令级作用域和会话授权。每个来源贡献独立的拒绝、询问和允许列表。当用户说"始终允许这个项目使用这个工具"时,授权被持久化到项目设置来源,保持用户级和会话级规则不变。这种可组合性允许团队发布限制性的项目级默认值,个别开发者可以在其用户设置中放宽而不冲突。