> ## Documentation Index
> Fetch the complete documentation index at: https://ccb.agent-aura.top/llms.txt
> Use this file to discover all available pages before exploring further.

# 权限模型 - Allow/Ask/Deny 三级权限体系

> 详解 Claude Code 的三级权限模型实现：基于 src/utils/permissions/permissions.ts 的规则匹配引擎、五层规则来源优先级、工具名/命令/路径三维度匹配、Denial Tracking 死循环防护、权限模式切换机制。

## 三种权限行为

每一次工具调用，系统都会做出三种裁决之一：

| 行为        | 含义         | 返回类型                                                  | 典型场景         |
| --------- | ---------- | ----------------------------------------------------- | ------------ |
| **Allow** | 自动放行，用户无感知 | `{ behavior: 'allow', updatedInput, decisionReason }` | Read 读取项目内文件 |
| **Ask**   | 弹出确认对话框    | `{ behavior: 'ask', message, suggestions, metadata }` | Bash 执行未知命令  |
| **Deny**  | 直接拒绝       | `{ behavior: 'deny', message, decisionReason }`       | 尝试执行被禁止的命令   |

这些行为由 `PermissionResult` 类型定义（`src/utils/permissions/PermissionResult.ts`）。

## 权限规则的来源

规则从 8 个来源汇聚（`PERMISSION_RULE_SOURCES`，`permissions.ts:109`），优先级从低到高（后者覆盖前者）：

```
1. userSettings   — ~/.claude/settings.json（跨项目）
2. projectSettings — .claude/settings.json（团队共享）
3. localSettings  — .claude/settings.local.json（gitignored，个人覆盖）
4. flagSettings   — --settings 命令行参数
5. policySettings — 企业管理员下发的策略（用户不可覆盖）
6. cliArg         — 命令行 --allow/--deny 参数
7. command        — Skill 工具的 allowedTools 白名单
8. session        — 用户在当前对话中手动授权（"Always allow"）
```

每个来源维护三个数组：`alwaysAllowRules[source]`、`alwaysAskRules[source]`、`alwaysDenyRules[source]`。

规则数据结构为 `PermissionRule`：

```typescript theme={null}
{
  source: PermissionRuleSource      // 来自哪个层级
  ruleBehavior: 'allow' | 'ask' | 'deny'
  ruleValue: {
    toolName: string                // 如 "Bash"、"mcp__server1"
    ruleContent?: string            // 如 "git *"、"src/**"
  }
}
```

## 规则匹配引擎

### 三维度匹配

`permissions.ts` 实现了三种匹配维度：

**1. 工具名匹配**（`toolMatchesRule()`，第 238 行）

匹配整个工具，仅当规则没有 `ruleContent`：

```typescript theme={null}
// 精确匹配
rule "Bash" → 匹配 BashTool
rule "mcp__server1" → 匹配该 MCP Server 的所有工具（server 级别）
rule "mcp__server1__*" → 通配符匹配（同上）
```

MCP 工具使用 `getToolNameForPermissionCheck()` 获取匹配名称，支持有前缀（`mcp__server__tool`）和无前缀模式。

**2. 命令模式匹配**（BashTool 的 `checkPermissions()`）

BashTool 通过 `preparePermissionMatcher()`（`Tool.ts:520`）解析命令模式：

```json theme={null}
{"tool": "Bash", "ruleContent": "git *"}  → 匹配 "git commit -m 'fix'"
```

命令通过 AST 解析（`readOnlyValidation.ts` 使用 tree-sitter bash），提取第一个子命令进行匹配。

**3. 路径匹配**（文件工具的 `checkPermissions()`）

Read/Edit/Write 工具通过 `getPath()` 提取文件路径，与 `ruleContent` 中的 glob 模式匹配：

```json theme={null}
{"tool": "Edit", "ruleContent": "src/**"}  → 匹配 "src/utils/foo.ts"
```

### 权限检查的完整流程

每次工具调用的权限检查（`canUseTool()` → `checkPermissions()`）经过以下步骤：

```
1a. Blanket deny 检查
    getDenyRuleForTool() → 工具名完全匹配 deny 规则？
    ↓ 命中 → deny（工具在 getTools() 阶段就被过滤掉）

1b. Blanket allow 检查
    toolAlwaysAllowedRule() → 工具名完全匹配 allow 规则？
    ↓ 命中 → allow

2. 工具自身 checkPermissions()
    各工具有自定义逻辑：
    - BashTool: readOnlyValidation → sandbox 判定 → AST 解析 → 模式匹配
    - FileEditTool: 路径白名单检查
    - SkillTool: safe properties 白名单 + 精确/前缀匹配
    ↓ 返回 PermissionResult

3. Hook 系统
    executePermissionRequestHooks() → PreToolUse hook 可以 override
    ↓ hook 返回 deny → deny
    ↓ hook 返回 ask → 升级为 ask

4. Ask 规则检查
    getAskRules() → 命中 → ask

5. 默认行为
    根据当前 permissionMode 决定默认行为
    - 'default': 大部分工具 ask
    - 'plan': 写操作 deny，读操作 allow
    - 'bypass': 全部 allow
```

## 权限模式

| 模式               | `PermissionMode` 值    | 适用场景  | 行为                                                                    |
| ---------------- | --------------------- | ----- | --------------------------------------------------------------------- |
| **Default**      | `'default'`           | 日常使用  | 敏感操作逐一确认                                                              |
| **Plan Mode**    | `'plan'`              | 探索阶段  | 只能读不能写（`isReadOnly()` 检查）                                             |
| **Accept Edits** | `'acceptEdits'`       | 快速迭代  | 工作区内文件编辑自动放行，其他操作仍需确认                                                 |
| **Don't Ask**    | `'dontAsk'`           | 减少打断  | 尽量自动决策，减少确认弹窗                                                         |
| **Auto**         | `'auto'`              | 信任 AI | 通过 transcript classifier 自动决策（需 `TRANSCRIPT_CLASSIFIER` feature flag） |
| **Bypass**       | `'bypassPermissions'` | 完全信任  | 所有操作自动放行（需显式 `--dangerously-skip-permissions`）                        |

Plan Mode 切换由 `EnterPlanModeTool.call()` 触发：

```typescript theme={null}
// EnterPlanModeTool.ts:88
context.setAppState(prev => ({
  ...prev,
  toolPermissionContext: applyPermissionUpdate(
    prepareContextForPlanMode(prev.toolPermissionContext),
    { type: 'setMode', mode: 'plan', destination: 'session' },
  ),
}))
```

退出时由 `ExitPlanModeV2Tool` 恢复为之前的模式。

## Denial Tracking：死循环防护

`src/utils/permissions/denialTracking.ts` 实现了拒绝追踪机制：

```typescript theme={null}
const DENIAL_LIMITS = {
  maxConsecutive: 3,        // 同一工具连续拒绝上限
  maxTotal: 20,             // 总拒绝上限
}
```

当 AI 被连续拒绝同一类操作达到上限时：

1. `recordDenial()` 记录拒绝，增加计数
2. `shouldFallbackToPrompting()` 检测到连续拒绝，返回 true
3. 系统向 AI 注入消息："Your previous tool call was rejected..."
4. AI 被迫改变策略，避免"反复请求同一个被拒操作"的死循环

操作成功时调用 `recordSuccess()` 重置计数。

## 规则的运行时更新

权限规则可以在运行时动态更新（`applyPermissionUpdate()`，`PermissionUpdate.ts`）：

```typescript theme={null}
type PermissionUpdate =
  | { type: 'addRules', destination, rules, behavior }
  | { type: 'replaceRules', destination, rules, behavior }
  | { type: 'removeRules', destination, rules, behavior }
  | { type: 'setMode', destination, mode }
  | { type: 'addDirectories', destination, directories }
  | { type: 'removeDirectories', destination, directories }
```

当用户在 Ask 对话框中选择 "Always allow"，系统调用 `persistPermissionUpdates()` 将规则写入对应层级的 settings 文件（project/user/managed），同时更新内存中的 `toolPermissionContext`。
