Permission Gate
Permission Gate 是工具执行前的安全边界。
没有 Permission Gate 的 Agent 很危险,因为模型可能请求:
- 删除文件。
- 覆盖配置。
- 运行危险 shell 命令。
- 访问用户没有预期授权的目录。
- 调用外部服务发送数据。
权限不应该写散
错误设计:
ts
class BashTool {
async execute(input) {
if (input.command.includes("rm -rf")) {
throw new Error("dangerous")
}
return run(input.command)
}
}1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
这种做法的问题是:
- 每个工具都要自己判断。
- 无法统一接用户确认 UI。
- 无法统一记录审计。
- 无法用配置管理 allow/deny。
更好的设计是:
text
ToolExecutor
-> PermissionGate
-> Tool.execute()1
2
3
2
3
工具描述“我要做什么”,权限网关决定“能不能做”。
Claude Code 的权限层次
Claude Code 的权限判断大致包含:
- 全局 deny。
- 全局 ask。
- 工具自己的
checkPermissions()。 - permission mode。
- Hook 决策。
- 用户确认。
- 安全检查。
更准确地说,源码里的权限链路会经过:
useCanUseTool():交互式路径里的权限入口。hasPermissionsToUseTool():核心权限判定。checkRuleBasedPermissions():匹配 allow、deny、ask 规则。- 工具自己的
checkPermissions()。 - permission mode 对 ask、deny、allow 的转换。
在非交互模式下,无法弹出 UI 的 ask 通常会变成 deny,或者交给外部 permission prompt 机制处理。
这不是为了复杂而复杂,而是因为真实 Agent 产品有多个授权来源:
- 用户临时确认。
- 用户长期配置。
- 项目配置。
- 企业策略。
- Hook。
- 非交互 SDK host。
简化版 PermissionGate
第一版可以这样设计:
ts
export type PermissionDecision =
| { type: "allow" }
| { type: "deny"; reason: string }
| { type: "ask"; question: string }
export class PermissionGate {
constructor(private rules: PermissionRule[]) {}
async check(tool: Tool, input: unknown, context: ToolContext) {
for (const rule of this.rules) {
const decision = rule.match(tool, input, context)
if (decision) return decision
}
if (tool.readonly) return { type: "allow" as const }
return {
type: "ask" as const,
question: `Allow ${tool.name}?`,
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
小结
权限系统的核心不是“拦几个危险命令”,而是让每次外部动作都经过统一、可审计、可配置的决策点。