Claude Code 源码解读 06:权限系统与 Hooks——安全与自动化的基石

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

alwaysAllowRulesalwaysDenyRulesalwaysAskRules

这三个规则集合是权限系统的核心决策单元。它们的优先级是: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 挡住 sudorm -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------以及它们之间的关系。

相关推荐
小虎AI生活1 小时前
养个 AI 合伙人:Hermes Agent 保姆级部署教程
ai编程
tanis_32 小时前
MCP 服务器配置:让 AI 助手直接解析 PDF 文档
ai编程·mcp
数据知道3 小时前
claw-code 源码分析:大型移植的测试哲学——如何用 unittest 门禁守住「诚实未完成」的口碑?
开发语言·python·ai·claude code·claw code
程序员鱼皮3 小时前
太秀了,我把自己蒸馏成了 Skill!已开源
ai·程序员·开源·编程·ai编程
Duran.L3 小时前
从限购到畅通:GLM-5.1 Coding Plan接入攻略
人工智能·ai·软件工程·个人开发·ai编程
数据知道3 小时前
claw-code 源码分析:结构化输出与重试——`structured_output` 一类开关如何改变「可解析性」与失败语义?
算法·ai·claude code·claw code
小程故事多_803 小时前
AI Coding 工程化革命,Superpowers 管流程,ui-ux-pro-max 管质感
人工智能·ui·架构·aigc·ai编程·ux·claude code
陈佬昔没带相机3 小时前
AI 编程更可控,GitHub 亲生子 Spec-kit 带给你优秀的 SDD 体验
ai编程