权限与审批

三种权限模式、规则 DSL、13 条策略的责任链、挂在执行前事件上的审批与否决,以及基于 tree-sitter-bash 的危险命令守卫。

作者 David更新于 第 17 篇(共 33 篇)

权限系统是 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';
模式界面名称启动参数语义
manualAlways Ask默认默认放行名单之外逐项确认
yoloAsk When Needed-y / --yolo常规操作自动执行,风险操作仍会询问
autoNever 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;
#策略表态
1auto-mode-ask-user-question-denyauto 模式下拒绝 AskUserQuestion
2user-configured-deny命中拒绝规则
3dangerous-command-ask危险或无法分析的 Bash 命令要询问;auto 模式下不表态,非交互启动时不装
4auto-mode-approveauto 模式放行
5session-approval-history命中「本会话允许」记录则放行
6user-configured-ask命中询问规则
7user-configured-allow命中放行规则
8sensitive-file-access-ask访问 .env、私钥等敏感文件要询问
9git-control-path-access-ask触碰 .git 或 git 控制目录要询问
10yolo-mode-approveyolo 模式放行
11default-tool-approve默认放行名单:Read、Grep、Glob、FetchURL、WebSearch、Agent 等 24 个工具
12git-cwd-write-approve写入都在工作区内、且位于 git 工作树中时放行 Write / Edit(仅 POSIX 路径)
13fallback-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):PreToolUse hook 给出拒绝理由就否决——用户可以写脚本来 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 集成。

本页目录