Claude Code 源码解读 06:权限系统与 Hooks------安全与自动化的基石
一个 Agent 能读什么文件、能执行什么命令、能调用什么工具------这些不是理所当然的。权限系统和 Hooks 共同构成了 Claude Code 的安全边界与自动化引擎。
先问一个问题
当你第一次运行 claude 时,它默认就能读取文件、执行 Bash 命令。但如果你把 Claude Code 交给团队使用,或者在 CI 环境中跑自动化脚本,这个"默认能做什么"就成了核心问题。
Claude Code 的答案是:分层控制。权限规则(Permission Rules)负责"谁能做什么",Hooks 负责"什么时候做什么"。两层机制互为补充,共同支撑起整个安全与自动化体系。
理解这两个子系统,你才能真正掌控 Claude Code------而不是被它掌控。
权限规则:三行配置挡掉灾难
先说权限系统,因为它更简单。Claude Code 的权限规则写在 settings.json 里:
json
{
"permissions": {
"allowed": [
"Read(*.md)",
"Bash(git *)",
"Bash(npm run *)"
],
"denied": [
"Read(.env*)",
"Bash(sudo *)",
"Bash(rm -rf /)"
]
}
}
规则匹配有完整的语法,核心是 工具(匹配模式) 的结构。Bash 工具还支持命令子串匹配------Bash(git *) 匹配所有以 git 开头的命令,Bash(git push) 则精确匹配 git push。
alwaysAllowRules、alwaysDenyRules、alwaysAskRules
这三个规则集合是权限系统的核心决策单元。它们的优先级是:alwaysDenyRules > alwaysAskRules > alwaysAllowRules。deny 永远优先。
typescript
// 权限检查的核心链(伪代码)
function resolvePermission(toolName, input) {
// 1. 检查 alwaysDenyRules --- 直接拒绝
if (matchRule(alwaysDenyRules, toolName, input)) {
return { behavior: 'deny' }
}
// 2. 检查 alwaysAskRules --- 需要用户确认
if (matchRule(alwaysAskRules, toolName, input)) {
return { behavior: 'ask' }
}
// 3. 检查 alwaysAllowRules --- 直接放行
if (matchRule(alwaysAllowRules, toolName, input)) {
return { behavior: 'allow' }
}
// 4. 走默认逻辑:模式特定检查 + 交互式询问
return tool.checkPermissions(input)
}
七种权限模式
Claude Code 不只是"允许/拒绝"二分法。它定义了七种权限模式,每种对应不同的使用场景:
| 模式 | 行为 |
|---|---|
default |
工具特定检查,未知操作弹窗询问用户 |
acceptEdits |
自动允许文件编辑,其他操作询问 |
plan |
只读模式,拒绝所有写操作 |
dontAsk |
自动拒绝所有需要询问的操作(用于后台 Agent) |
bypassPermissions |
完全绕过权限检查(用于自动化脚本) |
auto |
用 AI 分类器自动决定(实验性功能) |
bubble |
子 Agent 专有,将权限请求向上冒泡给父 Agent |
bubble 模式 是理解子 Agent 权限行为的关键。当你在主会话中启动一个子 Agent 处理后台任务时,子 Agent 的 bubble 模式会把权限请求(如"是否允许删除这个文件?")转发给主会话,由主会话的人类用户在终端里做决定。这是 Claude Code 处理"需要权限但没有终端"这个矛盾的方式。
BashTool 的命令解析器
Bash 工具的权限匹配最复杂,因为 shell 命令的形式太多了。parseForSecurity() 充当了一个 bash AST 解析器的角色,把复合命令(cd /tmp && mkdir build && ls build)拆成单个子命令,然后对每个子命令做安全分类。
typescript
// BashTool 内部的安全分类(简化)
const BASH_READ_COMMANDS = new Set(['ls', 'cat', 'grep', 'find', 'git', 'head', 'tail'])
const BASH_WRITE_COMMANDS = new Set(['rm', 'mv', 'cp', 'mkdir', 'touch', 'chmod'])
const BASH_DANGEROUS_COMMANDS = new Set(['sudo', 'dd', 'mkfs'])
function classifyCommand(cmd: string): 'read' | 'write' | 'dangerous' {
const base = cmd.split(' ')[0]
if (BASH_DANGEROUS_COMMANDS.has(base)) return 'dangerous'
if (BASH_WRITE_COMMANDS.has(base)) return 'write'
if (BASH_READ_COMMANDS.has(base)) return 'read'
return 'neutral' // echo, printf 等中性命令
}
复合命令只有在所有非中性部分都是只读时才被判定为只读。这防止了 rm -rf . && git status 这样的命令被误判为安全。
一个小细节:当 bash AST 解析失败时(比如命令里有 heredoc 或嵌套 subshell),匹配器返回
() => true------意味着总是触发 hook 运行。这是一种"宁可错杀,不可漏过"的安全哲学:太复杂的命令无法静态分析,就交给运行时钩子处理。
Hooks:事件驱动的自动化引擎
权限规则是静态的------写死在配置里。Hooks 则是动态的------在 Claude Code 的生命周期事件里插入自定义逻辑。
Hooks 写在 settings.json 里:
json
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash.*",
"hooks": [
{
"name": "validate-bash",
"type": "command",
"command": "/path/to/validate-bash.sh"
}
]
}
],
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"name": "auto-format",
"type": "command",
"command": "npx prettier --write ${target_path}"
}
]
}
]
}
}
matcher 使用 glob 模式匹配工具名称,type 决定处理器的执行方式。
四种 Handler 类型
Claude Code 的 Hooks 支持四种处理器,每种都有其适用场景:
1. Command(命令处理器)
最常用。执行一个 shell 脚本或命令,通过退出码决定行为:
bash
#!/bin/bash
# 返回 0 → 允许操作继续
# 返回 1 → 拒绝操作
# 返回 2 → 停止整个 Agent 循环
# 打印 JSON {"updated_input": {...}} → 修改工具输入后继续
2. Prompt(LLM 处理器)
不执行命令,而是把问题抛给另一个 LLM 实例做判断。适合需要语义理解的决策:
json
{
"type": "prompt",
"prompt": "Is this bash command safe to run in a production environment? Command: ${tool_input.command}"
}
3. HTTP(远程处理器)
把事件 POST 到外部服务,适合接入现有的安全扫描管道:
json
{
"type": "http",
"url": "https://security.internal/validate",
"headers": { "Authorization": "Bearer ${SECRET}" }
}
4. Agent(子 Agent 处理器)
用子 Agent 处理复杂决策。例如在安全审计 hook 里启动一个专门的分析 Agent:
json
{
"type": "agent",
"agent": "security-auditor",
"prompt": "Analyze this code change for security implications..."
}
Hook 生命周期完整图谱
Claude Code 目前支持 12 种 Hook 事件,按触发时机分:
会话生命周期:
SessionStart--- 会话启动时,适合加载额外上下文Stop--- 会话结束时,适合做清理或备份SubagentStart/SubagentStop--- 子 Agent 启停
工具生命周期:
PreToolUse--- 工具执行前,权限检查的最后关口PostToolUse--- 工具执行后,适合做格式化和验证
任务生命周期:
TaskCreated--- 任务被创建时TaskCompleted--- 任务完成时
环境感知:
CwdChanged--- 工作目录变化时FileChanged--- 文件被修改时Notification--- 发送通知前
上下文管理:
PreCompact--- 上下文压缩前,适合做快照PostCompact--- 上下文压缩后
用户交互:
UserPromptSubmit--- 用户提交提示词后,适合做输入验证
PreToolUse 的.updated_input 机制
最强大的 Hook 能力之一:PreToolUse 处理器可以通过返回 updated_input 来修改工具的输入参数:
bash
#!/bin/bash
# validate-and-sandbox.sh
# 检查 Bash 命令,如果是 sed,自动添加 -i 的安全参数
command=$(echo "$input" | jq -r '.command')
if [[ "$command" =~ ^sed ]]; then
# 限制 sed 只能操作特定目录
new_command=$(echo "$command" | sed 's|/home/sangchg/project|/home/sangchg/project|g')
echo '{"updated_input": {"command": "'"$new_command"'"}}'
exit 0
fi
exit 0 # 放行
这个能力让 Hooks 超越了纯粹的门卫角色------它们可以主动改造行为,而不只是放行或拒绝。
14 步执行管道里的 Hook 位置
回顾第五章的 Agent 循环,工具执行的 14 步管道中,Hook 出现在第 7 步(PreToolUse)和第 12 步(PostToolUse):
1. 工具查找
2. 中止检查
3. Zod 验证
4. 语义验证
5. 启动 Speculative 分类器(并行)
6. Input Backfill(填充派生字段)
7. ← PreToolUse Hooks(可修改输入、可拒绝、可停止循环)
8. 权限解析(hook 决策 > 规则匹配 > 工具检查 > 模式默认 > 交互询问)
9. 权限拒绝处理
10. 工具执行
11. 结果持久化(过大结果存磁盘)
12. ← PostToolUse Hooks(可修改结果、可阻止继续)
13. 新消息追加
14. 错误分类与遥测
Hook 出现在权限解析之前,这意味着它们拥有"比规则更早介入"的能力。一个 PreToolUse hook 可以比 alwaysDenyRules 更早拒绝操作,或者在规则之外做语义级别的判断(比如"这个 git push 是否force了?")。
权限 + Hooks 的组合拳
真正强大的用法是把权限规则和 Hooks 组合起来。规则负责粗粒度的黑白名单,Hooks 负责细粒度的语义判断:
json
{
"permissions": {
"allowed": ["Bash(git *)"],
"denied": ["Bash(sudo *)", "Bash(rm -rf /)"]
},
"hooks": {
"PreToolUse": [
{
"matcher": "Bash(git *)",
"hooks": [
{
"type": "command",
"command": "/path/to/hook-scripts/validate-git-force.sh"
}
]
}
]
}
}
alwaysDenyRules 挡住 sudo 和 rm -rf /,Hook 则负责在允许的 git 命令里进一步审查是否包含 --force 等危险选项。
为什么说 Hooks 是 Claude Code 的灵魂扩展机制
很多用户把 Claude Code 的可扩展性归功于 MCP------连接外部服务器的能力确实很酷。但 MCP 解决的是"工具种类"的问题(如何引入新工具),而 Hooks 解决的是"行为控制"的问题(现有工具怎么被使用)。
理解这一点很重要:MCP 让你告诉 Claude"可以做什么",Hooks 让你控制"怎么做"和"什么时候做"。两者结合,才构成完整的扩展体系。
Hooks 的另一个被低估的价值是上下文节约。以前你要在 CLAUDE.md 里写"每次编辑后运行 prettier",现在一个 PostToolUse Hook 自动完成这件事,不需要在每次对话里重复提醒模型。
下一章,我们来看看 Claude Code 的另一套扩展体系------插件、Skills 和 MCP------以及它们之间的关系。