# 权限与审批

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

- 作者：David（道雾轩）
- 专栏：Kimi Code 源码解读（https://daiw.org/manual/kimi-code.md）
- 最后更新：2026-09-19
- 原文：https://daiw.org/manual/kimi-code/permission
- 转载与引用：请注明出处并附原文链接（https://daiw.org/about/copyright）

权限系统是 agent 安全的核心。v0.29 的 `agent/permission/` 与 `PermissionManager` 随 v1 引擎删除；v2 把它拆成五个 Agent 作用域服务，都在 `agent-core-v2/src/agent/` 下：`permissionMode/`（当前模式）、`permissionRules/`（规则与「本会话允许」记录）、`permissionPolicy/`（策略责任链）、`permissionGate/`（挂到工具执行器上做裁决）、`toolApproval/`（向用户发审批请求）。设计仍是**策略责任链 + 规则 DSL**。

## 三种模式

```ts
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

```ts
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()` 解析：

```text
Write            裸工具名：匹配该工具的所有调用
Read(/etc/**)    工具名 + 参数子句
Bash(git *)      Bash 用 glob 匹配整条命令
mcp__github__*   工具名部分用 picomatch，可以通配
Bash(!rm *)      参数以 ! 开头表示取反
```

关键约定没变：**参数子句只有提供了 `matchesRule` 的工具才解释**，其余工具只按名字匹配，带参数的规则对它们永不命中。`Read` / `Write` / `Edit` 用 `matchesPathRuleSubject`（展开 `~`、相对路径按工作目录规范化、默认不区分大小写），`Bash`、`Grep`、`FetchURL` 等用 `matchesGlobRuleSubject`（见[前](https://daiw.org/manual/kimi-code/file-tools)[两章](https://daiw.org/manual/kimi-code/shell-tools)）。`reason` 会拼进拒绝消息，告诉模型「为什么不行」。

TOML 的 `[permission]` 段仍接受 `rules`，也接受 `deny` / `allow` / `ask` 简写（`tool` 与 `match` 拼成 `Tool(match)`）。不过在 v0.43.0 源码里，`IAgentPermissionRulesService.addRules()` 在整个仓库中没有调用方——配置规则如何进入规则状态，这一版代码里看不到对应链路。

## 13 条策略的责任链

`AgentPermissionPolicyService` 在构造时按固定顺序实例化策略，`evaluate()` 逐条询问，**第一个表态的策略说了算**：

```ts
    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` 事件：

```ts
    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](https://daiw.org/manual/kimi-code/hooks)。
- **swarm**（`features/swarm`）：`AgentSwarm` 必须独占一批调用。
- **目标**（`features/goal`）：`CreateGoal` 的启动审阅；此外还有重复调用检测（`agent/toolDedupe`）等。

权限仍在**执行之前**介入：被拒的调用根本不会执行，直接得到错误结果。

## 审批请求与「本会话允许」

```ts
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](https://daiw.org/manual/kimi-code/shell-tools) 解析命令（预算 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 集成](https://daiw.org/manual/kimi-code/mcp)。
