Claude Code权限系统评估顺序为deny→ask→allow,首个匹配即生效。广域deny会屏蔽窄域allow,两者同时存在时allow永远无法生效。bare tool-name deny会将工具完全移除,而非仅限制特定路径。
大多数关于 Claude Code 权限系统的文章都止步于"存在 allow、deny 和 ask 三种规则"——这个说法没错,但它跳过了在压力下真正决定你的配置能否站住脚的部分:规则有固定的求值顺序,hooks 与这个顺序以特定且不直观的方式交互,还有一小部分路径在任何规则下都无法解锁,除非完全绕过权限。以下是实践中容易出错的地方。
Claude Code 的求值顺序是:deny → ask → allow,匹配到的第一个规则生效。规则的精确度不会改变结果。一条宽泛的 Bash(aws *) deny 会阻止调用,即使同时存在一条非常精确的 Bash(aws s3 ls) allow。这是"为什么我的 allow 规则不生效"最常见的困惑来源——如果链中任何位置存在更宽泛的 deny,没有任何 allow 规则(无论多么精确)能穿透它。如果你在调试一条看似被忽略的规则,先检查是否存在 deny 匹配,不要急着怀疑 allow 语法有问题。
bare tool-name deny 和 scoped deny 之间还有一个值得内化的区别。"deny": ["Bash"] 会从 Claude 可用工具中彻底移除 Bash——这不是 Claude 会考虑的一个选项。Bash(rm *) 则保留 Bash 可用,但会在匹配的命令被执行时进行阻止。决定是彻底消除一个能力还是仅围栏危险子集,是一个真实的设计选择,而不仅仅是语法偏好。
有两类受保护路径的优先级低于规则被查询的层级。受保护路径——.git、.claude、.vscode、shell rc 文件、.mcp.json 以及类似项——在任何模式下都不会被任何 allow 规则自动批准(除非使用 bypassPermissions)。安全检查在 Claude Code 求值你的 allow 规则之前运行,所以在 settings.json 中添加 Edit(.claude/**) 条目对这个关卡毫无影响。这是刻意设计的:防止一条被入侵或配置错误的 allow 规则静默授予对定义 Claude Code 自身权限和 hooks 的文件的写访问权。
关键路径则是同一理念在破坏性删除上的应用:针对文件系统根目录、顶级目录(如 /usr 或 /etc)、主目录,或工作目录及其父目录的 rm/rmdir 操作会被拒绝——没有任何 allow 规则,也没有任何返回"allow"的 PreToolUse hook 可以批准这些操作,在任何模式下都不行,包括 bypass。这是一个硬编码的熔断器,用于防止模型错误,而不是你可以调整的策略设置。
权限规则是你写一次就不再变的固定列表。PreToolUse hooks 是在工具调用执行前运行的脚本,它们检查实际参数,根据静态规则无法表达的运行时上下文返回决策——扫描文件内容、调用验证服务、检查命令实际会触及什么。
这个交互值得精确记住:hook 的决策不会绕过权限规则。无论 PreToolUse hook 返回什么,Claude Code 仍然会求值 deny 和 ask 规则——一条匹配的 deny 规则会阻止调用,即使 hook 说了"allow"。反过来也一样:一条阻止性的 hook(退出码 2)会覆盖 allow 规则。这给你一个真正有用的模式——用一条宽泛的 allow 规则放行整个工具(如 "allow": ["Bash"]),然后注册一个 hook 只拒绝你真正想阻止的特定命令,而不是手工维护一个需要提前预判所有危险变体的穷举式 deny 列表:
#!/bin/bash
# .claude/hooks/block-rm.sh
COMMAND=$(jq -r '.tool_input.command')
if echo "$COMMAND" | grep -q 'rm -rf'; then
jq -n '{hookSpecificOutput:{hookEventName:"PreToolUse",
permissionDecision:"deny",
permissionDecisionReason:"Destructive command blocked by hook"}}'
fi
exit 0
退出码 2 是一个阻塞性错误——它直接阻止工具调用,即使 hook 同时输出了表示 permissionDecision": "allow" 的 JSON。任何其他非零退出码都被视为非阻塞:调用会像 hook 没有运行过一样,正常走权限流程。这个"退出码 2"和其他失败"的区别在赶工期时很容易记反,而记反的后果是:你写的用来阻止某事的 hook 实际上什么也没做。
用权限规则处理静态、无条件的判断:"永远不允许 Claude 读取 secrets/" 不依赖上下文,所以 deny 规则是正确的工具。用 hook 处理任何依赖检查命令实际内容的场景:"阻止任何涉及生产迁移路径的提交" 需要运行时上下文,这是名称匹配的规则无法表达的。确定性控制——deny 规则、可靠地在匹配时退出 2 的 hook——每次都成立,不管某次请求是怎么被推理的。分类器或行为良好的模型只是降低了问题发生的频率,但不是保证。
以上都不是沙箱的替代品,也不是完整的安全威胁模型——提示注入和 MCP 信任是各自独立的话题。但优先级顺序、受保护/关键路径,以及 hook 与规则的交互,是最容易导致安全策略看起来纸面上正确、实际上站不住脚的三个问题。我在 Claude Code Sandboxing and Security 中更完整地阐述了这些内容——沙箱范围、MCP 信任、管理设置、事件响应等。