权限与审批
三种权限模式、规则 DSL、13 条策略的责任链、挂在执行前事件上的审批与否决,以及基于 tree-sitter-bash 的危险命令守卫。
权限系统是 agent 安全的核心。v0.29 的 agent/permission/ 与 PermissionManager 随 v1 引擎删除;v2 把它拆成五个 Agent 作用域服务,都在 agent-core-v2/src/agent/ 下:permissionMode/(当前模式)、permissionRules/(规则与「本会话允许」记录)、permissionPolicy/(策略责任链)、permissionGate/(挂到工具执行器上做裁决)、toolApproval/(向用户发审批请求)。设计仍是策略责任链 + 规则 DSL。
三种模式
export type PermissionMode = 'manual' | 'yolo' | 'auto';| 模式 | 界面名称 | 启动参数 | 语义 |
|---|---|---|---|
manual | Always Ask | 默认 | 默认放行名单之外逐项确认 |
yolo | Ask When Needed | -y / --yolo | 常规操作自动执行,风险操作仍会询问 |
auto | Never Ask | --auto | 除了明确的拒绝规则,一律自动放行 |
默认模式也可以用 defaultPermissionMode 配置。两种放开模式的差别全在责任链的位置上:auto-mode-approve 排第 4,链内只有拒绝规则能拦住它;yolo-mode-approve 排第 10,排在它前面的危险命令、用户 ask 规则、敏感文件、.git 控制目录检查都会先问一遍。auto 模式下 AskUserQuestion 被直接拒绝(无人值守,没人能回答),计划审阅也自动通过。
规则 DSL
export interface PermissionRule {
readonly decision: PermissionRuleDecision;
readonly scope: PermissionRuleScope;
readonly pattern: string;
readonly reason?: string;
}decision 取 allow / deny / ask;scope 取 turn-override / session-runtime / project / user,三条 user-configured-* 策略只看其中的 turn-override、project、user,session-runtime 留给「本会话允许」。pattern 由 matchesRule.ts 的 parsePattern() 解析:
Write 裸工具名:匹配该工具的所有调用
Read(/etc/**) 工具名 + 参数子句
Bash(git *) Bash 用 glob 匹配整条命令
mcp__github__* 工具名部分用 picomatch,可以通配
Bash(!rm *) 参数以 ! 开头表示取反关键约定没变:参数子句只有提供了 matchesRule 的工具才解释,其余工具只按名字匹配,带参数的规则对它们永不命中。Read / Write / Edit 用 matchesPathRuleSubject(展开 ~、相对路径按工作目录规范化、默认不区分大小写),Bash、Grep、FetchURL 等用 matchesGlobRuleSubject(见前两章)。reason 会拼进拒绝消息,告诉模型「为什么不行」。
TOML 的 [permission] 段仍接受 rules,也接受 deny / allow / ask 简写(tool 与 match 拼成 Tool(match))。不过在 v0.43.0 源码里,IAgentPermissionRulesService.addRules() 在整个仓库中没有调用方——配置规则如何进入规则状态,这一版代码里看不到对应链路。
13 条策略的责任链
AgentPermissionPolicyService 在构造时按固定顺序实例化策略,evaluate() 逐条询问,第一个表态的策略说了算:
for (const policy of this.policies) {
const result = await policy.evaluate(context);
if (result !== undefined) return { policyName: policy.name, result };
}
return undefined;| # | 策略 | 表态 |
|---|---|---|
| 1 | auto-mode-ask-user-question-deny | auto 模式下拒绝 AskUserQuestion |
| 2 | user-configured-deny | 命中拒绝规则 |
| 3 | dangerous-command-ask | 危险或无法分析的 Bash 命令要询问;auto 模式下不表态,非交互启动时不装 |
| 4 | auto-mode-approve | auto 模式放行 |
| 5 | session-approval-history | 命中「本会话允许」记录则放行 |
| 6 | user-configured-ask | 命中询问规则 |
| 7 | user-configured-allow | 命中放行规则 |
| 8 | sensitive-file-access-ask | 访问 .env、私钥等敏感文件要询问 |
| 9 | git-control-path-access-ask | 触碰 .git 或 git 控制目录要询问 |
| 10 | yolo-mode-approve | yolo 模式放行 |
| 11 | default-tool-approve | 默认放行名单:Read、Grep、Glob、FetchURL、WebSearch、Agent 等 24 个工具 |
| 12 | git-cwd-write-approve | 写入都在工作区内、且位于 git 工作树中时放行 Write / Edit(仅 POSIX 路径) |
| 13 | fallback-ask | 兜底:问用户 |
git-cwd-write-approve 的判据依旧聪明:在 git 仓库里改文件是可恢复的,用版本控制的存在作为安全边界。fallback-ask 作为链尾保证默认安全:没有策略表态时问用户,而不是默认放行。
与 v0.29 的 17 个策略文件相比,plan-mode-*、exit-plan-mode-review-ask、goal-start-review-ask、agent-swarm-exclusive-deny、pre-tool-call-hook 从链里消失了——它们没有被删掉,而是搬进了各自的 feature,挂在同一个执行前事件上;deny-all 则不复存在。
挂在执行前事件上的裁决
AgentPermissionGate 不直接包住工具,而是订阅工具执行器的 onBeforeExecuteTool 事件:
const { result, policyName } = evaluation;
if (result.kind === 'ask') {
event.waitUntil(() => this.toolApproval.requestToolApproval(event, result, policyName));
return;
}
if (result.kind === 'approve') {
event.pass(result.executionMetadata);
return;
}这个事件的语义是:监听者依次执行,任何一个 veto() 立即否决,allow() 立即放行并跳过其余检查;waitUntil() 登记的异步检查(比如弹审批框)等所有监听者跑完后才依次执行。同一个事件上还挂着:
- 计划模式(
features/plan):写计划文件的Write/Edit直接allow(),写别处的一律否决,TaskStop、CronCreate、CronDelete也被否决;非 auto 模式下ExitPlanMode登记计划审阅。 - 外部 hooks(
features/externalHooks):PreToolUsehook 给出拒绝理由就否决——用户可以写脚本来 gate 危险调用,详见 Hooks。 - swarm(
features/swarm):AgentSwarm必须独占一批调用。 - 目标(
features/goal):CreateGoal的启动审阅;此外还有重复调用检测(agent/toolDedupe)等。
权限仍在执行之前介入:被拒的调用根本不会执行,直接得到错误结果。
审批请求与「本会话允许」
export interface ApprovalRequest {
id?: string;
toolCallId: string;
toolName: string;
action: string;
display: ToolInputDisplay;
}需要询问时,AgentToolApprovalService 经 interactions.request()(kind: 'approval')把请求发给接入的客户端,并广播 permission.approval.requested / permission.approval.resolved 事件,外部 hooks 据此触发 PermissionRequest / PermissionResult。请求带 ToolInputDisplay,前端能可读地展示「即将执行什么」。被拒时模型收到「the user rejected the approval request」和用户填写的理由;子代理还会多一句「换个思路,不要重试、不要绕过限制」。
用户选「本会话允许」(scope: 'session')时,记下的是这次执行的 approvalRule,例如 Bash(git status) 这样的字面量规则,经持久事件 permission.record_approval_result 写进可回放的 agent 状态,之后由 session-approval-history 匹配。它只在会话内有效,不写进配置。
v0.29 的 PermissionManager 用 parent 指针让子代理继承父会话权限;v2 改为子代理创建时复制调用方的模式(以及用户工具),批准记录不继承。主 agent 切换模式时会广播给其他活跃 agent,tower worker 除外。
危险命令守卫
dangerous-command-ask 用 tree-sitter-bash 解析命令(预算 500 毫秒 / 10000 个节点),识别关机重启、mkfs、写裸设备的 dd、/tmp 以外的 rm -rf 等,并能穿透 sudo、env、bash -c、eval、busybox 等包装。解析被中止或有语法错误时,命令被视为「无法分析」,同样要询问——宁可多问一次,也不猜。
CHANGELOG 记录了它的演进:v0.40.0 引入时,auto 模式直接拦截,manual / yolo 模式询问;v0.41.0 起 auto 模式不再拦截。开关是 [permission] dangerous_command_guard,或环境变量 KIMI_CODE_DANGEROUS_COMMAND_GUARD。
下一篇:MCP 集成。