05-权限不是开关:工具调用如何在放行、拦截与追问之间做决定

05-权限不是开关:工具调用如何在放行、拦截与追问之间做决定

本章大纲

  1. 从一次"拒绝还是确认"的请求开始,拆解权限管线的多阶段决策。
  2. 区分 deny、ask、allow 与 passthrough 的语义和覆盖关系。
  3. 追踪规则来源,理解会话级配置为何可能改变最终结果。
  4. 通过并发检查说明 ToolPermissionContext 为什么必须提供稳定快照。
  5. 分析 Hook、分类器和用户之间如何用 ResolveOnce 竞争一次决策。
  6. 对比不同权限模式保留的边界,判断自动化究竟放宽了什么。
  7. 将 Bash 权限细化到命令匹配粒度,并观察超时后的回退路径。
  8. 区分内存更新与持久化更新,确认权限变化能否跨越重启保存。

1. 一次请求为什么会从拒绝转成追问

工具调用并不只有"允许"和"拒绝"两个出口。它可能先经过输入结构验证,再进入规则匹配;没有被前面阶段终结时,才会继续接受工具上下文评估或交互式处理。

输入验证使用 Zod Schema。输入不符合结构定义时,调用通常会被拒绝。

但解析失败并不只有硬拒绝这一条路径。默认的 passthrough 可能降级为 ask,把决定交给用户确认。

checkPermissions 的结果包括 allow、deny、ask 和 passthrough。其中 allow 还可能携带经过更新的输入,passthrough 则表示当前检查没有给出最终意见。

概念上,可以把一次请求理解为四个职责阶段:输入验证、规则匹配、工具上下文评估、交互式提示。只要前面的阶段已经作出终局决定,后面的阶段就不会继续执行。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-1.svg" alt="05-权限管线-Agent-的护栏 图 1" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

这张图表达的是职责上的阶段模型。实现层面还需要保留一个差异:源文将 checkPermissions 概述为第三阶段,但实现说明指出,它会在规则匹配流程中较早调用,结果还可能被后续的 allow 规则覆盖。因此,不能把这四个阶段机械理解成所有代码路径都严格串行。

2. 四种结果并不等价

deny、ask、allow 和 passthrough 代表不同的决策意图,而不是四种可以互换的状态。

deny 表示拒绝。规则匹配时,行为优先级是 deny > ask > allow,显式拒绝不能由允许规则覆盖。

ask 表示当前结果必须经过确认。它不是"暂时没有结论",而是明确要求用户或其他后续决策者参与。

passthrough 的含义不同:当前工具不表达自身意见,把处理权交给后续阶段。后续的 allow 规则可能覆盖它,但不能覆盖明确的 ask。

因此,"进入人工处理"不能概括所有非放行结果。一个请求可能是工具暂不表态,也可能是工具已经明确要求确认。

json 复制代码
{
  "permissions": {
    "allow": [
      "Bash(npm test)",
      "Bash(npm run lint)",
      "Bash(git:*)",
      "Read",
      "Glob",
      "Grep"
    ],
    "deny": [
      "Bash(npm publish)",
      "Bash(rm -rf *)"
    ]
  }
}

这份配置同时展示了不同粒度的规则:既有对 Read、Glob、Grep 这类工具的整体放行,也有针对具体 Bash 命令的限制。

allow 还可能附带更新后的输入,所以它不一定只是一个布尔结论。相反,passthrough 只说明当前检查没有做出最终判断。

3. 规则结果还取决于它从哪里来

同一个工具、同一组参数,在不同项目或不同会话中得到不同结果,并不一定意味着工具行为改变了。规则来源本身就是决策的一部分。

规则来源包括:

  • userSettings
  • projectSettings
  • localSettings
  • flagSettings
  • policySettings
  • cliArg
  • command
  • session

这些来源大体遵循从全局到局部、从远处到当前会话的就近关系。会话级优先级最高,全局用户级优先级最低。

因此,不能只把配置理解为用户、项目、本地三层。命令行参数、命令级设置、策略设置和功能标志也可能参与最终匹配。

规则按来源建立索引后,可以追踪一条决定来自全局、项目、本地,还是其他配置层。这个信息对于解释"为什么这次结果不同"十分关键。

工具级规则既可以匹配整个工具,也可以使用 MCP 服务器级通配匹配。

Bash 存在一个有条件的例外:在沙箱模式下,如果启用了沙箱自动放行,沙箱命令可能跳过工具级的 ask 规则。这里的前提是沙箱隔离已经降低了命令影响范围,不能把它推广为普通 Bash 请求的通用行为。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-2.svg" alt="05-权限管线-Agent-的护栏 图 2" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

第二份配置展示了通配规则如何扩大覆盖面:

json 复制代码
{
  "permissions": {
    "allow": [
      "Bash(npm run *)",
      "Bash(npx eslint *)"
    ],
    "deny": [
      "Bash(npm publish *)",
      "Bash(* > /etc/*)"
    ]
  }
}

Bash(npm run *) 比 Bash(npm run lint) 覆盖更广。规则越宽,能够自动放行的命令范围越大,误放行的潜在范围也随之扩大。

4. 不可变上下文如何保护并发检查

设想两个工具调用同时运行,其中一个正在更新权限。如果另一个调用直接读取同一个可变对象,它可能在一次检查过程中看到前后不一致的规则状态。

ToolPermissionContext 携带权限模式、目录、allow、deny、ask 规则、bypass 可用性和提示控制等信息。

这些字段是只读的。每次更新都会生成新的上下文对象,而不是就地修改当前对象。

这样,每个并发检查都可以使用自己的稳定快照。一个工具的权限更新不会改变另一个工具已经开始执行的检查。

不可变性解决的是权限上下文更新导致的状态污染问题。它并不等于自动解决所有并发问题,源文只论述了权限快照一致性的边界。

权限决策的来源包括 Hook、用户和分类器。它们的信任顺序是 Hook 高于用户,用户高于分类器。这个顺序与上下文快照解决的是不同问题:前者决定谁更可信,后者保证检查过程中读到的状态稳定。

5. 一次确认只能由一个决策者提交

请求进入 ask 后,权限管线可能同时触发 Hook、分类器和用户交互。

Hook 可以先给出结果。如果 Hook 没有处理,请求可能继续触发投机性分类器检查,并同时渲染交互式提示。

此时用户和分类器存在竞赛关系。谁先完成有效决策,谁的结果就对这次请求生效。

ResolveOnce 通过 claimed 标志和 claim() 实现一次性、先到先得的提交机制。它的目标是防止用户和分类器同时解决同一个请求。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-3.svg" alt="05-权限管线-Agent-的护栏 图 3" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

用户决策还带有 permanent 属性,用于区分这次选择是否需要写入配置。

分类器的参与并不代表它拥有最高信任级别。某些安全检查会限制分类器决策,后文的模式分析也会说明,自动批准并不能覆盖所有防线。

6. 权限模式不是一条简单的宽松刻度

权限模式包括 default、plan、auto、bypassPermissions,以及内部使用的 bubble。

default 通常要求没有被明确允许的工具调用逐次确认。它的安全性较高,但大量操作会增加交互中断。

plan 主要限制写入工具,读取工具通常可用。不过,在 bypass 可用标志为真的情况下,部分 bypass 放行逻辑仍可能生效,因此不能把它简单理解为"所有写入都绝对禁止"。

auto 使用 AI 分类器处理部分 ask 请求,并结合安全工具白名单、快速路径和连续拒绝回退机制减少开销或避免循环。

自动分类器并不拥有全部安全路径的批准权。涉及 .git/、.claude/ 的某些安全检查不受分类器批准影响,这些检查可以视为"分类器免疫"的部分路径,而不是所有敏感操作的统一规则。

bypassPermissions 通常会跳过其他权限确认,但仍保留 deny、交互要求、内容级 ask 和 safetyCheck 等防线。因此,它不等于完全没有安全约束。

bubble 是内部模式。子智能体的权限向主智能体上下文冒泡,不应超出主智能体边界;它也不是对外公开的普通配置模式。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-4.svg" alt="05-权限管线-Agent-的护栏 图 4" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

这张图表达的是从严格到宽松的使用倾向,不应被当作所有模式在任何场景下都能互相替代。每种模式仍保留自己的例外和边界。

7. Bash 必须细化到命令匹配粒度

Bash 不能只按照工具名称做一次统一放行,因为同一个工具入口可以承载风险差异很大的命令。

它支持精确匹配、前缀匹配和通配符匹配。

精确匹配只覆盖特定命令,例如 Bash(npm test)。前缀匹配可以覆盖一组以相同前缀开头的命令,例如 Bash(npm:*)。通配符匹配则能覆盖更宽的参数范围,例如 Bash(git commit *)。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-5.svg" alt="05-权限管线-Agent-的护栏 图 5" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

表达能力越强,匹配面越宽,潜在误放行范围也越大。Bash 通配匹配还会处理转义字符,特定尾部通配写法可以让参数部分变成可选。

Bash 权限请求还可能触发投机性分类器检查。分类器与 2 秒定时器竞争;如果分类器在限定时间内没有完成,系统就转交交互式提示。

这个 2 秒是交互体验上的权衡,不应被描述成经过外部验证的普遍最优标准。

8. 当前生效不代表重启后仍然存在

权限更新包括添加规则、替换规则、移除规则、设置模式、添加目录和移除目录六类操作。

这些操作可以作用于五类配置源,因此形成 30 种可能的更新组合。但"存在一种组合"不代表每个来源都支持持久化。

applyPermissionUpdates 负责立即更新内存中的权限上下文,persistPermissionUpdates 负责把更新写入文件系统。

内存更新先于持久化生效。即使文件写入失败,当前会话仍然使用新的权限状态;但重启后,未成功保存的规则可能丢失。

只有 local、user、project 三类设置支持持久化。session 和 cliArg 级别的更新只在当前运行时有效。
<figure class="columnforge-visual"><img src="../assets/05-权限管线-Agent-的护栏/05-权限管线-Agent-的护栏-6.svg" alt="05-权限管线-Agent-的护栏 图 6" loading="lazy" decoding="async" style="display:block;width:100%;max-width:960px;max-height:720px;height:auto;object-fit:contain;margin-inline:auto"></figure>

配置位置可以按使用范围选择。团队共享规则适合放在项目级配置,个人偏好适合放在本地配置,临时需求适合使用会话级规则。

这里需要区分"当前调用已经看到变化"和"文件系统已经保存变化"。前者由内存更新保证,后者只有在支持持久化的配置源写入成功后才成立。源文没有说明持久化失败后的重试、并发写入、崩溃恢复或文件一致性策略,因此不能把 persistPermissionUpdates 描述成可靠落盘机制。

结语:把每次决定定位到具体边界

遇到一次权限请求,可以依次追问几个问题:

  • 输入是否符合 Zod Schema 的结构定义?
  • 哪一层规则先作出了决定?
  • 结果是 passthrough,还是明确的 ask?
  • 规则来自哪个配置源?
  • 当前检查使用的是不是稳定的 ToolPermissionContext 快照?
  • Hook、用户和分类器中,谁获得了这次决策权?
  • 当前模式保留了哪些 deny、交互要求或 safetyCheck 防线?
  • Bash 规则覆盖的是单条命令、命令前缀,还是更宽的通配范围?
  • 更新只改变了内存,还是已经写入 local、user、project 配置?

可靠的权限系统并不是让所有请求自动通过,而是让放行、拒绝和追问都能落到清楚的规则、明确的上下文与可追踪的状态变化上。

相关推荐
AllData公司负责人13 分钟前
AI大模型数据开发底座系列说明书:AllData集成DB-GPT开源项目建设 AI 多模态分析底座,涵盖模态解析/智能大屏/MCP 扩展!
大数据·数据库·人工智能·gpt·数据采集·数据接入·模态解析
泯泷29 分钟前
RL 训练真正难的不是算法:环境、奖励与样本效率
llm·agent
syyjhchou2 小时前
自己搭企业内部中转站,怎么让请求更容易命中缓存
人工智能·后端·语言模型·aigc
张然Adolf2 小时前
人工智能电力系统应用新手实战指南
人工智能
高洁012 小时前
数据同化与动态状态估计:让孪生持续贴合现实
人工智能·python·深度学习·transformer·迁移学习
feasibility.2 小时前
一物降一物?AI逆向没有终局:REA拆App,BLA设套,LASIK清场
人工智能·爬虫·github·逆向·skill·rea
威哥爱编程3 小时前
模拟面试智能体开发实战 07:答完一题就点评一题,结构化输出给分建议,让每道题都变成复盘
python·agent·harmonyos
IT古董3 小时前
AI 资讯日报 | 2026年10月25日:Kimi K3.1 Pro开源逼近前沿,OpenAI改离线训练,Claude合并Cowork,神经网络黑箱获新解,具身智能竞技场落幕
人工智能·神经网络·开源
威哥爱编程3 小时前
模拟面试智能体开发实战 08:总评报告先过三道闸,防泄题防夸大防错建议,才敢发到候选人手上
python·agent·harmonyos