Rule System
Claude Code 的 permission 系统由 rule 驱动——这些声明式语句表达”允许此 tool”、“拒绝该命令”或”运行前始终询问”。rule 可来自 8 个不同来源,各自具有不同的持久化特性和信任级别。当 rule 冲突时,确定性优先级系统会解决歧义。
Rule 结构
Section titled “Rule 结构”每条 permission rule 包含三个组件:
export type PermissionRule = { source: PermissionRuleSource ruleBehavior: PermissionBehavior // 'allow' | 'deny' | 'ask' ruleValue: PermissionRuleValue}
export type PermissionRuleValue = { toolName: string // "Bash"、"Edit"、"mcp__server__tool" ruleContent?: string // 可选:"npm install:*"、"git commit"}Rule 字符串格式
Section titled “Rule 字符串格式”rule 以字符串形式持久化在 settings 文件中,由 permissionRuleParser.ts 解析:
export function permissionRuleValueFromString(ruleString: string): PermissionRuleValue { // 查找第一个未转义的左括号 const openParenIndex = findFirstUnescapedChar(ruleString, '(') if (openParenIndex === -1) { return { toolName: normalizeLegacyToolName(ruleString) } } // 提取 tool 名称和内容 const toolName = ruleString.substring(0, openParenIndex) const rawContent = ruleString.substring(openParenIndex + 1, closeParenIndex) // 空内容或单独的通配符 → tool 级别规则 if (rawContent === '' || rawContent === '*') { return { toolName: normalizeLegacyToolName(toolName) } } return { toolName, ruleContent: unescapeRuleContent(rawContent) }}rule 内容中的括号用反斜杠转义:
// Bash(python -c "print(1)") 存储为:// Bash(python -c "print\(1\)")export function escapeRuleContent(content: string): string { return content .replace(/\\/g, '\\\\') // 先转义反斜杠 .replace(/\(/g, '\\(') // 转义左括号 .replace(/\)/g, '\\)') // 转义右括号}Rule 匹配模式
Section titled “Rule 匹配模式”rule 通过 ShellPermissionRule 以三种模式匹配命令:
| 模式 | 语法 | 示例 | 匹配内容 |
|---|---|---|---|
| 精确匹配 | command | Bash(npm install express) | 仅 npm install express |
| 前缀匹配 | command:* | Bash(npm install:*) | npm install express、npm install -D jest 等 |
| 通配符 | pattern*pattern | Bash(*test*) | 任意包含 “test” 的命令 |
export function parsePermissionRule(rule: string): ShellPermissionRule { // 检查 :* 后缀 → 前缀规则 // 检查 * → 通配符规则 // 否则 → 精确匹配}8 种规则来源
Section titled “8 种规则来源”rule 来自 8 个来源,分布在两个类型系统中:
export const SETTING_SOURCES = [ 'userSettings', // 1. 用户 settings(全局) 'projectSettings', // 2. 项目 settings(共享) 'localSettings', // 3. 本地 settings(gitignored) 'flagSettings', // 4. Flag settings(--settings flag) 'policySettings', // 5. Policy settings(受管理/企业)] as const
// src/types/permissions.ts — 额外的规则特定来源export type PermissionRuleSource = | SettingSource // 以上全部 5 个 | 'cliArg' // 6. CLI 参数 | 'command' // 7. 会话内命令 | 'session' // 8. 会话期间用户批准来源 1:用户 Settings(userSettings)
Section titled “来源 1:用户 Settings(userSettings)”位置:~/.claude/settings.json
范围:全局——适用于所有项目
持久性:永久,直到手动编辑
显示名称:“user settings”
{ "permissions": { "allow": ["Edit", "Bash(git:*)"], "deny": ["Bash(rm -rf:*)"], "ask": [] }}来源 2:项目 Settings(projectSettings)
Section titled “来源 2:项目 Settings(projectSettings)”位置:项目根目录的 .claude/settings.json
范围:当前项目——跨团队共享
持久性:提交到版本控制
显示名称:“shared project settings”
团队使用此文件为项目定义基准规则:
{ "permissions": { "allow": ["Bash(npm test:*)", "Bash(npm run lint:*)"], "deny": ["Bash(npm publish:*)"] }}来源 3:本地 Settings(localSettings)
Section titled “来源 3:本地 Settings(localSettings)”位置:项目根目录的 .claude/settings.local.json
范围:当前项目,仅限本机
持久性:gitignored——不共享
显示名称:“project local settings”
“始终允许” prompt 中的用户批准通常保存在此处:
{ "permissions": { "allow": ["Bash(python3:*)", "Bash(cargo build:*)"] }}来源 4:Flag Settings(flagSettings)
Section titled “来源 4:Flag Settings(flagSettings)”位置:--settings CLI flag 指定的文件路径
范围:当前会话
持久性:文件存在期间有效
显示名称:“command line arguments”
claude --settings /path/to/custom-settings.json来源 5:Policy Settings(policySettings)
Section titled “来源 5:Policy Settings(policySettings)”位置:managed-settings.json 或远程 API
范围:企业级
持久性:由 IT 管理
显示名称:“enterprise managed settings”
当 allowManagedPermissionRulesOnly 启用时,此来源覆盖所有其他来源:
export function loadAllPermissionRulesFromDisk(): PermissionRule[] { if (shouldAllowManagedPermissionRulesOnly()) { return getPermissionRulesForSource('policySettings') } // 否则,从所有启用的来源加载}来源 6:CLI 参数(cliArg)
Section titled “来源 6:CLI 参数(cliArg)”范围:当前会话 持久性:仅命令行
claude --allowed-tools "Bash(git:*),Edit"claude --denied-tools "Bash(curl:*)"来源 7:会话内命令(command)
Section titled “来源 7:会话内命令(command)”范围:当前会话 持久性:仅内存
> /allowed-tools Bash(npm test:*)来源 8:会话批准(session)
Section titled “来源 8:会话批准(session)”范围:当前会话 持久性:仅内存(退出后丢失)
当用户以”是,本次允许”批准 permission prompt 时,rule 存储在 session 来源中。
优先级与合并算法
Section titled “优先级与合并算法”Behavior 优先级:Deny > Ask > Allow
Section titled “Behavior 优先级:Deny > Ask > Allow”当同一命令匹配到不同 behavior 的 rule 时,优先级始终为:
- Deny — 最先检查,立即生效
- Ask — 次之检查,触发 permission prompt
- Allow — 最后检查,自动批准
整个代码库中均强制执行此规则:
// bashToolCheckPermission (src/tools/BashTool/bashPermissions.ts)// 1. 先精确匹配(deny > ask > allow)// 2. 前缀/通配符 deny → DENY// 3. 前缀/通配符 ask → ASK// 4. 路径约束// 5. 前缀/通配符 allow → ALLOW相同 Behavior 下的来源优先级
Section titled “相同 Behavior 下的来源优先级”在相同 behavior 中(例如两条来自不同来源的 allow rule),先匹配者获胜。rule 按顺序从所有来源收集:
const PERMISSION_RULE_SOURCES = [ ...SETTING_SOURCES, // userSettings、projectSettings、localSettings、flagSettings、policySettings 'cliArg', 'command', 'session',] as const satisfies readonly PermissionRuleSource[]SETTING_SOURCES 的顺序在 constants.ts 中定义:
// src/utils/settings/constants.ts (line 7)export const SETTING_SOURCES = [ 'userSettings', // 第一个(settings 优先级最低,但 rule 使用先匹配) 'projectSettings', 'localSettings', 'flagSettings', 'policySettings', // 最后(信任度最高)] as const规则收集流程
Section titled “规则收集流程”flowchart TD
A[Tool invocation] --> B[Collect ALL rules from ALL sources]
B --> DenyRules["Deny Rules\n(all sources merged)"]
B --> AskRules["Ask Rules\n(all sources merged)"]
B --> AllowRules["Allow Rules\n(all sources merged)"]
DenyRules --> D1{Any deny\nmatches?}
D1 -->|Yes| DENY[DENY]
D1 -->|No| A1
AskRules --> A1{Any ask\nmatches?}
A1 -->|Yes| ASK[ASK]
A1 -->|No| L1
AllowRules --> L1{Any allow\nmatches?}
L1 -->|Yes| ALLOW[ALLOW]
L1 -->|No| PASSTHROUGH[No rule matched\nfallback to mode]
不可绕过的安全检查
Section titled “不可绕过的安全检查”某些 permission 检查不能被任何 rule 或 mode 覆盖。这些是系统的最终安全网:
1. 敏感路径安全检查
Section titled “1. 敏感路径安全检查”.git/、.claude/、.vscode/ 中的文件以及 shell 配置文件(.bashrc、.zshrc、.profile)始终触发 permission prompt,即使在 bypassPermissions 模式下也如此:
// src/utils/permissions/permissions.ts (step 1g)if (toolPermissionResult?.behavior === 'ask' && toolPermissionResult.decisionReason?.type === 'safetyCheck') { return toolPermissionResult // 不可绕过}2. 内容特定的 Ask Rule
Section titled “2. 内容特定的 Ask Rule”当用户显式配置含内容的 ask rule(如 Bash(npm publish:*))时,即使在 bypass 模式下也会生效:
// Step 1f:内容特定的 ask ruleif (toolPermissionResult?.behavior === 'ask' && toolPermissionResult.decisionReason?.type === 'rule' && toolPermissionResult.decisionReason.rule.ruleBehavior === 'ask') { return toolPermissionResult // 即使在 bypass 模式下也会遵守}3. 进入 Auto Mode 时的危险 Pattern 剥离
Section titled “3. 进入 Auto Mode 时的危险 Pattern 剥离”切换到 auto mode 时,危险 allow rule 会被主动移除:
export function isDangerousBashPermission(toolName: string, ruleContent?: string): boolean { // Bash(无内容)= 全部允许 → 危险 // Bash(python:*) = 允许解释器 → 危险 // Bash(git commit:*) = 特定命令 → 安全}危险 pattern 包括:
- Tool 级别 allow(无内容的
Bash) - 解释器前缀:
python:*、node:*、ruby:*、perl:*等 - Eval 等价命令:
eval:*、exec:*、env:*、xargs:* - Shell 启动器:
bash:*、sh:*、zsh:* - 远程执行:
ssh:* - 权限提升:
sudo:*
Settings 文件中的 Rule 格式
Section titled “Settings 文件中的 Rule 格式”rule 存储在 settings JSON 文件的 permissions 键中:
{ "$schema": "https://json.schemastore.org/claude-code-settings.json", "permissions": { "allow": [ "Edit", "Bash(git:*)", "Bash(npm test:*)", "Bash(npm run lint:*)", "mcp__server1" ], "deny": [ "Bash(rm -rf:*)", "Bash(npm publish:*)", "Bash(curl:*)" ], "ask": [ "Bash(docker:*)" ] }}MCP Server Rule
Section titled “MCP Server Rule”MCP(Model Context Protocol)tool 支持 server 级别规则:
// src/utils/permissions/permissions.ts (line ~259)// rule "mcp__server1" 匹配 tool "mcp__server1__tool1"const ruleInfo = mcpInfoFromString(rule.ruleValue.toolName)const toolInfo = mcpInfoFromString(nameForRuleMatch)return ruleInfo !== null && toolInfo !== null && (ruleInfo.toolName === undefined || ruleInfo.toolName === '*') && ruleInfo.serverName === toolInfo.serverName| Rule | 匹配内容 |
|---|---|
mcp__server1 | server1 的所有 tool |
mcp__server1__* | server1 的所有 tool(通配符变体) |
mcp__server1__read | 仅 server1 的 read tool |
Legacy Tool 名称别名
Section titled “Legacy Tool 名称别名”tool 名称经过标准化以保持向后兼容:
const LEGACY_TOOL_NAME_ALIASES: Record<string, string> = { Task: AGENT_TOOL_NAME, // "Task" → "Agent" KillShell: TASK_STOP_TOOL_NAME, // "KillShell" → "TaskStop" AgentOutputTool: TASK_OUTPUT_TOOL_NAME, BashOutputTool: TASK_OUTPUT_TOOL_NAME,}Rule 持久化与更新
Section titled “Rule 持久化与更新”Permission 更新操作
Section titled “Permission 更新操作”当 rule 发生变化(用户批准、settings 编辑、CLI 命令)时,更新通过类型化操作系统流转:
export type PermissionUpdate = | { type: 'addRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior } | { type: 'replaceRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior } | { type: 'removeRules'; destination: PermissionUpdateDestination; rules: PermissionRuleValue[]; behavior: PermissionBehavior } | { type: 'setMode'; destination: PermissionUpdateDestination; mode: ExternalPermissionMode } | { type: 'addDirectories'; destination: PermissionUpdateDestination; directories: string[] } | { type: 'removeDirectories'; destination: PermissionUpdateDestination; directories: string[] }磁盘同步算法
Section titled “磁盘同步算法”当 settings 文件在磁盘上发生变化(热重载)时,系统执行完整替换同步:
export function syncPermissionRulesFromDisk( toolPermissionContext: ToolPermissionContext, rules: PermissionRule[],): ToolPermissionContext { let context = toolPermissionContext
// 当 allowManagedPermissionRulesOnly 时,清除所有非 policy 来源 if (shouldAllowManagedPermissionRulesOnly()) { for (const source of sourcesToClear) { for (const behavior of behaviors) { context = applyPermissionUpdate(context, { type: 'replaceRules', rules: [], behavior, destination: source, }) } } }
// 应用新规则前清除所有基于磁盘的来源 // 这确保已删除的 rule 被真正移除 for (const diskSource of ['userSettings', 'projectSettings', 'localSettings']) { for (const behavior of ['allow', 'deny', 'ask']) { context = applyPermissionUpdate(context, { type: 'replaceRules', rules: [], behavior, destination: diskSource, }) } }
// 应用新规则 const updates = convertRulesToUpdates(rules, 'replaceRules') return applyPermissionUpdates(context, updates)}删除 Rule
Section titled “删除 Rule”rule 可从可编辑来源(用户、项目、本地)删除,但不能从只读来源(policy、flag)删除:
export async function deletePermissionRule({ rule, ... }): Promise<void> { if (rule.source === 'policySettings' || rule.source === 'flagSettings' || rule.source === 'command') { throw new Error('Cannot delete permission rules from read-only settings') } // 更新内存中的 context const updatedContext = applyPermissionUpdate(initialContext, { type: 'removeRules', rules: [rule.ruleValue], behavior: rule.ruleBehavior, destination: rule.source as PermissionUpdateDestination, }) // 对基于文件的来源持久化到磁盘 switch (destination) { case 'localSettings': case 'userSettings': case 'projectSettings': deletePermissionRuleFromSettings(rule) break case 'cliArg': case 'session': break // 仅内存,无磁盘持久化 }}常用 Rule 配置
Section titled “常用 Rule 配置”开发团队基准配置
Section titled “开发团队基准配置”{ "permissions": { "allow": [ "Edit", "Bash(git:*)", "Bash(npm test:*)", "Bash(npm run:*)", "Bash(npx:*)" ], "deny": [ "Bash(npm publish:*)", "Bash(rm -rf:*)" ] }}企业锁定配置
Section titled “企业锁定配置”{ "allowManagedPermissionRulesOnly": true, "permissions": { "allow": [ "Bash(git status)", "Bash(git diff:*)", "Bash(git log:*)" ], "deny": [ "Bash(curl:*)", "Bash(wget:*)", "Bash(ssh:*)" ] }}个人开发者覆盖配置
Section titled “个人开发者覆盖配置”在 .claude/settings.local.json(gitignored)中:
{ "permissions": { "allow": [ "Bash(docker compose:*)", "Bash(python3:*)" ] }}Settings 来源与范围控制
Section titled “Settings 来源与范围控制”--setting-sources flag 控制加载哪些来源:
export function parseSettingSourcesFlag(flag: string): SettingSource[] { // "user,project,local" → ['userSettings', 'projectSettings', 'localSettings']}
export function getEnabledSettingSources(): SettingSource[] { const allowed = getAllowedSettingSources() // 始终包含 policy 和 flag settings const result = new Set<SettingSource>(allowed) result.add('policySettings') result.add('flagSettings') return Array.from(result)}Policy 和 flag settings 始终加载,不受 --setting-sources flag 影响。这确保企业策略无法被本地配置绕过。
rule 系统通过以下方式实现安全目标:
- Behavior 优先级(deny > ask > allow)确保最严格的规则始终胜出
- 来源多样性(8 种来源)支持个人、团队和企业工作流
- 不可绕过的检查即使在完全信任模式下也保护关键系统路径
- 危险 pattern 检测防止过宽规则破坏 auto mode
- 热重载无需重启会话即可保持规则最新
- Legacy 兼容无缝标准化旧版 tool 名称