权限设计

一次工具调用要走完的六道关

任何一关不放行,后面就不会发生。

不管工具来自内置、技能、插件还是 MCP,都走同一条路。

六道关

第一关 · 任务策略 taskPolicy

这次会话被允许做什么:只读计划模式、可写、还是全放行。计划模式不放宽任何权限,它只是把交互方式归一化成「先问再做」,越权直接拒绝。

第二关 · 预执行守卫 preToolGuards

按工具类别的专用守卫:桌面动作走 desktop-guard,联网走网络策略,文件写入走工作区边界。每一类风险单独判定,不合并成一个「总开关」——合并之后,日志里分不清一次拒绝挡的到底是什么。守卫拒绝即终止。

第三关 · 生命周期钩子门禁 pre_tool_use

钩子只能是宿主进程内注册的 JS 函数,不接受文件路径、命令行,也不接受来自模型或 HTTP 的代码。

门禁钩子同步执行、可以拒绝,并且失败关闭:钩子抛异常或返回非法值,一律阻止这次调用,而不是当作「没关系」继续。内置两条安全钩子——阻止破坏性系统命令与内联凭据、阻止指向云元数据端点的联网目标。

第四关 · 权限判定 perms

三层安全模式在这里生效。插件、MCP、联网各自独立判定,不共用一层。理由是它们的语义不同:「本机同进程的第三方代码」「外部进程」「出网」是三个不同的风险面,混在一起会让审计日志失真。

第五关 · 一次性审批 onConfirm

workspace 模式下每个动作单独确认。没有「记住本次选择」——一次性就是一次性。不批就不执行。

第六关 · 执行 exec-tool

只有前五关全部通过,工具函数才会被调用。插件工具在这一步拿到的也只是一个冻结的极小上下文:

{plugin_id, tool, effect, sessionId, taskId, signal}

拿不到权限模块,拿不到工作区句柄,拿不到确认回调。

三层安全模式

模式行为
safe 只读工作区只读、写入要确认、内置命令与自动验收禁止。插件等外接代码根本不会被执行。首次安装的默认档。
workspace 可写需确认工作区可读写,命令与自动验收需确认;插件工具每次调用都要一次性审批。多数人的日常状态。
trusted 放行全部放行。MCP 工具只在这一层才可能生效——它需要你明确配置服务器清单并接受信任边界。

fail-closed

当宿主没有接入任何权限判定时,用户的调用会被拒绝,而不是放行。

这是刻意的取舍:宁可一个漏接线的宿主暂时丢掉这个功能,也不给它一条未经判定的执行路径。一处接线遗漏的代价,应该是「功能不可用」,而不是「任意执行」。

代价

这套结构确实更啰嗦——多一个工具就要多写一处守卫、多一条测试。换来的是每一层拒绝都能被单独解释:是任务策略挡的、守卫挡的、钩子挡的、还是你在第五关点了「不批」。

为什么不能合成一层

把六道关卡压成一个「安全检查函数」看起来更简洁,但会丢掉三件事:

  • 拒绝理由的来源。 合成之后只能报「被拒绝」,无法回答「被哪一层拒绝」。
  • 不同风险面的独立演进。 联网策略要跟着 SSRF 手法更新,插件策略要跟着权限模型更新,两者节奏不同。
  • 降级的粒度。 你现在可以只关掉联网工具而保留本地文件能力;合成一层之后只能全开或全关。

这篇讲的是权限设计的一部分。回文章列表,或回首页看整体。