permission-rules:细粒度权限规则的匹配引擎
Claude Code 能够安全地在你的代码库运行 rm -rf,靠的不是 LLM 的自觉,而是一套严密的权限规则匹配引擎。无论处于哪种模式,任何工具执行前都要经过这道安检。
核心概念
本质上,它是一个 基于层级优先级的过滤链(Filter Chain)。
它将“工具名称”和“工具参数”作为输入,匹配预设的规则集(Ruleset),最终产出 allow(放行)、deny(拒绝)或 ask(弹窗请示)三种裁决结果。
代码里的真实逻辑
核心逻辑封装在 claude-code-opensource/src/utils/permissions/permissions.ts。
1. 规则的数据结构
每一条规则(PermissionRule)由以下部分组成(见 PermissionRule.ts):
toolName:匹配的工具(如bash、ls)。ruleContent:匹配模式(Pattern),通常是 Glob 或正则(如/usr/bin/*)。ruleBehavior:allow|deny|ask。source:来源(优先级关键),包括managed(企业管控)、cliArg(命令行)、projectSettings、userSettings。
2. 匹配流程(Matching Logic)
当工具请求执行时,hasPermissionsToUseToolInner 函数会按优先级扫描:
- Deny First:如果命中任何
deny规则,直接拒绝。 - Ask Second:如果命中
ask规则,无论该工具是否属于默认放行范畴,都强制弹窗。 - Bypass Mode 特权:如果用户启动了
--bypass-permissions,除了某些涉及核心安全的safetyCheck(如修改.claude配置本身),其余一律allow。 - Allow Last:命中
allow规则则放行。 - Default Action:如果没有任何规则命中,则降级到
PermissionMode的默认行为(如default模式下为ask)。
3. 参数级匹配(Argument Pattern Matching)
对于 bash 这种危险工具,引擎会深入参数。源码调用 shellRuleMatching.ts 中的 matchWildcardPattern。它支持通配符匹配,比如允许执行 npm test 但拦截 npm publish。这在 src/utils/permissions/filesystem.ts 中针对文件路径读写权限有大量实现。
实战注意事项
- 来源优先级(Source Precedence): Managed Settings > CLI Args > Project Settings > User Settings。 如果你的公司管理员禁用了
bash,你在命令行加-y也是没用的。 - Bash 的“虚假”匹配: Claude Code 并不是简单地字符串匹配 Bash 命令。它会先通过
extractOutputRedirections解析命令,识别出它是在读文件还是写文件,然后去调对应的文件权限规则。 - Auto 模式下的隐式放行: 在
auto模式下,如果规则没有显式定义,引擎会求助于classifierDecision.ts(基于 LLM 的分类器)来决定是否自动放行。
接下来看什么
- 想看 Bash 具体的命令过滤逻辑?查看
claude-code-opensource/src/utils/permissions/shellRuleMatching.ts。 - 想看自动模式的 AI 判定?查看
claude-code-opensource/src/utils/permissions/classifierDecision.ts。
源码锚点
- claude-code-opensource/src/utils/permissions/permissions.ts:1158 —
hasPermissionsToUseToolInner核心判定逻辑。
📄 src/utils/permissions/permissions.ts — `hasPermissionsToUseToolInner` 核心判定逻辑。L1148-1168 of 1487
typescript
toolPermissionResult?.behavior === 'ask' &&
toolPermissionResult.decisionReason?.type === 'safetyCheck'
) {
return toolPermissionResult
}
// No rule-based objection
return null
}
async function hasPermissionsToUseToolInner(
tool: Tool,
input: { [key: string]: unknown },
context: ToolUseContext,
): Promise<PermissionDecision> {
if (context.abortController.signal.aborted) {
throw new AbortError()
}
let appState = context.getAppState()1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- claude-code-opensource/src/utils/permissions/PermissionRule.ts:35 — 权限规则 Schema 定义。
📄 src/utils/permissions/PermissionRule.ts — 权限规则 Schema 定义。L25-41 of 41
typescript
export const permissionBehaviorSchema = lazySchema(() =>
z.enum(['allow', 'deny', 'ask']),
)
/**
* PermissionRuleValue is the content of a permission rule.
* @param toolName - The name of the tool this rule applies to
* @param ruleContent - The optional content of the rule.
* Each tool may implement custom handling in `checkPermissions()`
*/
export const permissionRuleValueSchema = lazySchema(() =>
z.object({
toolName: z.string(),
ruleContent: z.string().optional(),
}),
)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- claude-code-opensource/src/utils/permissions/filesystem.ts:1340 — 针对文件 IO 的分级权限检查。
📄 src/utils/permissions/filesystem.ts — 针对文件 IO 的分级权限检查。L1330-1350 of 1778
typescript
message: safetyCheck.message,
suggestions: safetySuggestions,
decisionReason: {
type: 'safetyCheck',
reason: safetyCheck.message,
classifierApprovable: safetyCheck.classifierApprovable,
},
}
}
// 2. Check for ask rules - check both the original path and resolved symlink path
for (const pathToCheck of pathsToCheck) {
const askRule = matchingRuleForInput(
pathToCheck,
toolPermissionContext,
'edit',
'ask',
)
if (askRule) {
return {
behavior: 'ask',1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21