AI 编程安全指南:用 Hook 机制给 Claude Code 加上"刹车"和"安全带"
2026/4/17
Read This Article in English前言
“Claude Code 写代码确实快,但它万一执行了个
rm -rf /怎么办?”
这是很多刚上手 Claude Code 的开发者最真实的焦虑。
AI 编程工具越来越强,自动读文件、写代码、跑命令,效率确实起飞。但问题也随之而来 —— 你没法控制它的每一个动作。它改了代码你希望自动格式化,它执行 Bash 命令你希望做安全检查,它写完文件你希望扫描有没有泄露密钥……
如果你有前端经验,你一定熟悉 Vue 的 mounted、beforeDestroy 这些生命周期钩子;如果你写过后端,Spring 的 @Before、@After AOP 切面肯定不陌生。它们的共同本质是:在特定事件节点,注入自定义逻辑。 Claude Code 的 Hook 机制,做的正是这件事。
今天这篇文章,我会基于 Anthropic 官方文档和 GitHub 热门教程 claude-howto,用你最熟悉的前后端概念做类比,带你彻底搞懂 Claude Code 的 Hook 机制,并给出 6 个可以直接复制使用的实战配置。
一、什么是 Hook?一句话讲透
Hook = 在特定事件发生时,自动执行你预设的逻辑。
不管是 Vue、Spring 还是 Claude Code,核心思想一模一样:
| 框架 | Hook 机制 | 本质 |
|---|---|---|
| Vue | mounted()、updated() | 组件生命周期的回调函数 |
| React | useEffect(() => {}, []) | 函数组件的副作用钩子 |
| Spring | @Before、@AfterReturning | AOP 切面,拦截方法调用前后 |
| Claude Code | PreToolUse、PostToolUse | 工具调用生命周期的事件钩子 |
用一张图理解 Claude Code 的 Hook 工作流:

看到了吗?这和 Spring AOP 的 @Before → 方法执行 → @After 思路非常相似。
二、Hook 的四种类型
Claude Code 提供了四种 Hook 类型,满足不同场景:
| 类型 | 说明 | 类比 |
|---|---|---|
| command | 执行 Shell 脚本,通过 stdin 接收 JSON,通过 exit code 返回决策 | 最通用,就像 Shell 脚本 |
| http | POST JSON 到远程 URL,适合对接内部系统 | 像 Webhook,通知你的服务 |
| prompt | 让 LLM 评估当前状态,返回决策 | 让 AI 自己判断该不该继续 |
| agent | 启动一个专门的子代理,可以调用工具做多步推理 | prompt 的加强版,能动手验证 |
重点区别:prompt 只能”想”,agent 能”想”还能”做”。比如你要验证代码是否通过测试,prompt 只能看文本判断,agent 可以真的跑一遍测试。
上下文获取方式:不同类型获取事件上下文的方式不同:
| 类型 | 上下文来源 | 示例 |
|---|---|---|
command | 通过 stdin 接收 JSON(用 cat 读取) | input=$(cat) |
http | POST 请求的 body 中携带 JSON | 服务端从 request body 解析 |
prompt | 模板变量 $ARGUMENTS 自动注入 | "prompt": "检查任务是否完成。$ARGUMENTS" |
agent | 模板变量 $ARGUMENTS 自动注入 | "prompt": "跑一遍测试。$ARGUMENTS" |
其中 $ARGUMENTS 是 Claude Code 的内置模板变量,运行时会被替换为当前 Hook 事件的 JSON 输入数据(包含会话 ID、工具名、工具参数等字段,具体内容因事件类型而异)。如果 prompt 字符串中没有写 $ARGUMENTS,JSON 输入会自动追加到 prompt 末尾。这样 LLM 就能基于真实的事件上下文做判断,而不是只靠你写的静态 prompt。
三、26 个生命周期事件全景图
Claude Code 目前支持 26 个 Hook 事件,按阶段分为 7 组。先看全景图,有个整体印象:

上图从上到下就是一次 Claude Code 会话的完整生命周期。我按组快速过一下各自的职责:
🔄 会话生命周期(6 个):管理会话本身的状态 —— 启动、结束、加载配置、切换目录、监听文件变化。大多数不可拦截,属于”通知型”事件。
🔧 工具调用生命周期(5 个):这是最核心的一组。PreToolUse 在工具执行前拦截(类比 @Before),PostToolUse 在执行后追加上下文(类比 @AfterReturning),PostToolUseFailure 处理异常(类比 @AfterThrowing)。日常 80% 的 Hook 都挂在这里。
🤖 子代理与团队(3 个):当 Claude Code 启动子代理或团队协作时触发。SubagentStop 可以拦截子代理的输出,TeammateIdle 可以在队友空闲时分配新任务。
📝 用户交互(2 个):UserPromptSubmit 是用户提交提示词时的”入口检查”,可以在这里拦截危险操作意图(比如”删库跑路”)。
🛑 停止与收尾(4 个):Stop 是 Claude Code 完成回复时的”出口检查”,可以用 prompt 类型让 AI 自己审查是否真的完成了任务。
🌲 Worktree 与压缩(4 个):管理 Git Worktree 和上下文压缩。PreCompact 可以在压缩前注入需要保留的关键信息。
🔗 MCP 交互(2 个):当 MCP 服务器请求用户输入时触发,适合做输入校验或自动填充。
> 记忆技巧:安全拦截和自动化这块,重点掌握 PreToolUse、PostToolUse、UserPromptSubmit 和 Stop 这四个事件就能覆盖大部分场景。但其他事件同样有实战价值 —— 比如 SessionStart 常被用来注入会话历史上下文,Notification 用来做桌面通知提醒,SubagentStop 用来审查子代理输出。按需取用就好。
四、六个实战场景,直接复制使用
🛡️ 场景一:PreToolUse 拦截危险 Bash 命令
类比:Spring 的 @Before 权限校验切面,方法执行前先检查权限。
在 Claude Code 执行 Bash 命令之前,先检查命令是否危险。
配置 settings.json:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/pre-tool-check.sh\""
}
]
}
]
}
}
对应的拦截脚本:
#!/bin/bash
# 文件:.claude/hooks/pre-tool-check.sh
input=$(cat)
command=$(echo "$input" | jq -r '.tool_input.command')
# 🚫 直接拦截的危险命令(使用 grep -F 做字面量匹配)
blocked_patterns=(
"rm -rf /"
"rm -rf /*"
":(){ :|:& };:"
"mkfs."
"dd if=/dev/zero"
"dd if=/dev/random"
)
for pattern in "${blocked_patterns[@]}"; do
if echo "$command" | grep -qF "$pattern"; then
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"deny\",\"permissionDecisionReason\":\"⛔ 检测到危险命令: $command\"}}"
exit 0
fi
done
# ⚠️ 警告但放行的命令(使用 grep -E 做正则匹配)
warn_patterns=("rm -rf" "git push --force" "git reset --hard" "DROP TABLE" "sudo rm")
for pattern in "${warn_patterns[@]}"; do
if echo "$command" | grep -qE "$pattern"; then
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\"permissionDecision\":\"allow\",\"additionalContext\":\"⚠️ 警告: 检测到敏感操作 $pattern,但已放行\"}}"
exit 0
fi
done
exit 0
✨ 场景二:PostToolUse 自动格式化代码
类比:Vue 的 updated() 钩子,DOM 更新后自动执行副作用。
每次 Claude Code 用 Write 或 Edit 工具修改文件后,自动运行格式化工具:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/format-code.sh\""
}
]
}
]
}
}
对应的格式化脚本:
#!/bin/bash
# 文件:.claude/hooks/format-code.sh
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
ext="${file_path##*.}"
case "$ext" in
js|ts|jsx|tsx) npx prettier --write "$file_path" 2>/dev/null ;;
py) python3 -m black "$file_path" 2>/dev/null ;;
go) gofmt -w "$file_path" 2>/dev/null ;;
java) google-java-format -i "$file_path" 2>/dev/null ;;
rs) rustfmt "$file_path" 2>/dev/null ;;
esac
exit 0
效果:Claude Code 写的代码永远保持格式统一,再也不用人工跑 formatter。
🔍 场景三:PostToolUse 安全扫描
类比:Spring 的 @AfterReturning 日志切面,方法正常返回后记录日志。
扫描 Claude Code 写入的文件,检测是否包含硬编码的密钥和密码。
配置 settings.json:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/security-scan.sh\""
}
]
}
]
}
}
对应的安全扫描脚本:
#!/bin/bash
# 文件:.claude/hooks/security-scan.sh
input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path')
# 跳过敏感目录
[[ "$file_path" =~ \.env|\.git/ ]] && exit 0
warnings=""
# 检查常见密钥模式(注意:macOS BSD grep 不支持 \x27,用单引号变量代替)
QUOTE="'"
if grep -qE "(password|passwd|pwd)\s*=\s*[\"${QUOTE}][^\"${QUOTE}]+[\"${QUOTE}]" "$file_path" 2>/dev/null; then
warnings+="⚠️ 发现硬编码密码\n"
fi
if grep -qE "(api_key|apikey|secret_key|access_token)\s*=\s*[\"${QUOTE}][^\"${QUOTE}]+[\"${QUOTE}]" "$file_path" 2>/dev/null; then
warnings+="⚠️ 发现硬编码 API 密钥\n"
fi
if grep -qE '-----BEGIN (RSA |EC )?PRIVATE KEY-----' "$file_path" 2>/dev/null; then
warnings+="🚨 发现私钥文件\n"
fi
if [ -n "$warnings" ]; then
# 以 additionalContext 形式返回,不阻断流程
echo "{\"hookSpecificOutput\":{\"hookEventName\":\"PostToolUse\",\"additionalContext\":\"安全扫描发现以下问题:\n$warnings\"}}"
fi
exit 0
📊 场景四:Stop 用 prompt hook 检查任务完成度
类比:这在 Vue/Spring 中没有直接对应,这是 Claude Code 独有的 —— 让 AI 自己审自己。
当 Claude Code 认为自己完成工作时,让 LLM 再评估一遍:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "prompt",
"prompt": "请评估 Claude 是否完成了用户要求的所有任务。检查以下几点:1) 是否所有文件都已修改 2) 是否有遗漏的功能 3) 测试是否通过。如果任务未完成,说明还差什么。$ARGUMENTS",
"timeout": 30
}
]
}
]
}
}
更强大的 agent 版本:真的跑一遍测试来验证:
{
"hooks": {
"Stop": [
{
"hooks": [
{
"type": "agent",
"prompt": "运行项目测试套件,验证所有测试是否通过。如果有失败的测试,报告失败原因。$ARGUMENTS",
"timeout": 120
}
]
}
]
}
}
🔔 场景五:Notification 桌面通知
类比:Vue 的 watch 响应式监听,数据变化时触发回调。
当 Claude Code 需要你的注意时(比如权限弹窗、长时间等待),发送 macOS 桌面通知:
{
"hooks": {
"Notification": [
{
"matcher": "",
"hooks": [
{
"type": "command",
"command": "osascript -e 'display notification \"Claude Code 需要你的关注\" with title \"Claude Code\" sound name \"default\"'"
}
]
}
]
}
}
Linux 用户替换为:
"command": "notify-send 'Claude Code' '需要你的关注'"
📝 场景六:UserPromptSubmit 拦截危险提示词
在用户提交提示词时就拦截危险操作意图。
配置 settings.json:
{
"hooks": {
"UserPromptSubmit": [
{
"hooks": [
{
"type": "command",
"command": "bash \"$CLAUDE_PROJECT_DIR/.claude/hooks/validate-prompt.sh\""
}
]
}
]
}
}
对应的提示词校验脚本:
#!/bin/bash
# 文件:.claude/hooks/validate-prompt.sh
input=$(cat)
prompt=$(echo "$input" | jq -r '.prompt')
blocked_keywords=("删除数据库" "drop database" "rm -rf /" "format c:" "删库跑路")
for keyword in "${blocked_keywords[@]}"; do
if echo "$prompt" | grep -qi "$keyword"; then
echo "{\"decision\":\"block\",\"reason\":\"⛔ 你的提示词包含危险操作: $keyword,已被安全钩子拦截。\"}" >&2
exit 2
fi
done
exit 0
五、配置指南
配置文件位置
Hooks 可以配置在多个位置,优先级从高到低:
| 位置 | 作用范围 | 是否可提交到 Git |
|---|---|---|
~/.claude/settings.json | 所有项目 | 否(个人设置) |
.claude/settings.json | 当前项目 | 是(团队共享) |
.claude/settings.local.json | 当前项目 | 否(本地覆盖) |
核心配置结构
{
"hooks": {
"事件名称": [
{
"matcher": "工具名模式",
"hooks": [
{
"type": "command",
"command": "你的脚本路径",
"timeout": 60
}
]
}
]
}
}
Matcher 模式规则
> 注意:UserPromptSubmit、Stop、TeammateIdle、TaskCreated、TaskCompleted、WorktreeCreate、WorktreeRemove、CwdChanged 这些事件不支持 matcher,每次触发时都会执行,配置了 matcher 也会被静默忽略。Matcher 主要用于工具类事件(如 PreToolUse、PostToolUse 等)。
| 模式 | 说明 | 示例 |
|---|---|---|
| 精确匹配 | 只匹配指定工具 | "Write" |
| 多选匹配 | 管道符分隔 | "Edit|Write" |
| 正则匹配 | 包含特殊字符则按正则解析 | "^Notebook" |
| 通配符 | 匹配所有 | "*" 或 "" |
| MCP 工具 | 匹配 MCP 服务器工具 | "mcp__memory__.*" |
Hook 脚本的核心机制
输入:通过 stdin 接收 JSON,包含工具名、参数、会话 ID 等。
输出:通过 exit code 和 JSON stdout 控制行为:
exit 0→ 继续(stdout 会被解析为 JSON,可通过hookSpecificOutput返回决策)exit 2→ 阻断操作(stderr 会显示为错误信息)- 其他 exit code → 非阻断错误,继续执行
环境变量:
$CLAUDE_PROJECT_DIR— 项目根目录的绝对路径(所有 command hook 均可用)$CLAUDE_ENV_FILE— 用于持久化环境变量的文件路径(仅在SessionStart、CwdChanged、FileChanged事件的 command hook 中可用)
快速上手步骤
# 1. 创建 hooks 目录
mkdir -p .claude/hooks
# 2. 复制你需要的脚本
# (从上面六个场景中选)
# 3. 赋予执行权限
chmod +x .claude/hooks/*.sh
# 4. 配置 settings.json
# (把对应的 JSON 配置加进去)
# 5. 测试 Hook
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | bash .claude/hooks/pre-tool-check.sh
echo $?
# 应该输出 2(被拦截)
总结
Hook 机制是 Claude Code 从”AI 黑盒”变成”可控流程”的关键桥梁。
回顾一下我们今天讲的:
- Hook 的本质就是事件驱动的回调,和你熟悉的 Vue 生命周期、Spring AOP 是同一个思路
- 四个核心事件覆盖 80% 场景:
PreToolUse(执行前拦截)、PostToolUse(执行后处理)、UserPromptSubmit(输入校验)、Stop(完成检查) - 四种 Hook 类型满足不同需求:command(最通用)、http(对接外部系统)、prompt(AI 自评)、agent(AI 自验证)
- 六个实战配置可以直接复制到你的项目中使用
正如 Spring AOP 让企业级应用有了统一的事务管理和安全控制,Claude Code 的 Hook 让 AI 编程有了安全网和质量门。
你不需要每件事都盯着 AI 做 —— 配好 Hook,让它在你设定的规则里自由发挥就好。
参考资料:
Anthropic 官方 Hooks 文档: https://code.claude.com/docs/en/hooks
Claude Code Hooks 实战教程(英文): https://github.com/luongnv89/claude-howto/tree/main/06-hooks
Claude Code Hooks 中文翻译版: https://github.com/lhfer/claude-howto-zh-cn/tree/main/06-hooks
欢迎关注公众号 FishTech Notes,一块交流使用心得!