第 5 课:Hook 安全管控
为什么 Hook 是 DevOps 助手的灵魂?
AI 能执行 Bash 命令,这很强大,但也很危险。你需要一个"安检系统":
没有 Hook:
AI: "我来清理一下临时文件"
AI: Bash → rm -rf / ← 😱 灾难!
有了 Hook:
AI: "我来清理一下临时文件"
AI: Bash → rm -rf /
Hook: 🚫 "拦截!危险命令!" ← 😮💨 救命!
Hook 就是 AI 工具调用的"拦截器"——在 AI 用工具之前检查、之后审计。
Hook 类型一览
| Hook | 什么时候触发 | 你能干嘛 |
|---|---|---|
PreToolUse |
AI 要用工具之前 | 拦截危险操作、自动批准安全操作 |
PostToolUse |
工具执行完之后 | 审计日志、给 AI 额外反馈 |
UserPromptSubmit |
用户发消息时 | 注入额外上下文 |
SubagentStart |
子 Agent 启动时 | 跟踪子 Agent |
Stop |
AI 停止工作时 | 清理资源 |
DevOps 最常用的是前两个:PreToolUse(拦截)和 PostToolUse(审计)。
PreToolUse:命令拦截
最基本的拦截
from claude_agent_sdk.types import HookInput, HookContext, HookJSONOutput
async def safety_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""拦截危险的 Bash 命令"""
tool_name = input_data["tool_name"]
tool_input = input_data["tool_input"]
# 只关心 Bash 命令
if tool_name != "Bash":
return {}
command = tool_input.get("command", "")
# 危险命令黑名单
dangerous_patterns = [
"rm -rf /", # 删除根目录
"rm -rf ~", # 删除用户目录
"dd if=/dev/zero", # 覆盖磁盘
"> /dev/sda", # 破坏磁盘
"chmod -R 777", # 权限全开
":(){ :|:& };:", # Fork 炸弹
]
for pattern in dangerous_patterns:
if pattern in command:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"危险命令已拦截: {pattern}",
}
}
# 不在黑名单里,放行
return {}
注册 Hook
from claude_agent_sdk import ClaudeAgentOptions
from claude_agent_sdk.types import HookMatcher
options = ClaudeAgentOptions(
allowed_tools=["Bash", "Read", "Write"],
hooks={
"PreToolUse": [
HookMatcher(
matcher="Bash", # 只匹配 Bash 工具
hooks=[safety_hook], # 用这个函数检查
),
],
}
)
HookMatcher 有两个字段:
- matcher:匹配哪些工具。"Bash" = 只匹配 Bash;None = 匹配所有工具
- hooks:匹配后用哪些函数来检查
自动批准安全操作
除了拦截危险操作,Hook 还能自动批准安全操作,免去人工确认:
async def auto_approve_safe_commands(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""自动批准只读命令"""
tool_name = input_data["tool_name"]
tool_input = input_data["tool_input"]
# 读文件:自动批准
if tool_name in ["Read", "Grep"]:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "只读操作,自动批准",
}
}
# 安全的 Bash 命令:自动批准
if tool_name == "Bash":
command = tool_input.get("command", "")
safe_prefixes = ["ls", "cat", "grep", "find", "ps", "df", "du",
"git status", "git log", "git diff", "docker ps"]
if any(command.strip().startswith(p) for p in safe_prefixes):
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "安全命令,自动批准",
}
}
# 其他情况:不表态(交给默认权限系统处理)
return {}
PostToolUse:审计日志
import datetime
import json
audit_log = []
async def audit_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""记录所有工具调用"""
tool_response = input_data.get("tool_response", "")
log_entry = {
"time": datetime.datetime.now().isoformat(),
"tool": input_data.get("tool_name", "unknown"),
"input": str(input_data.get("tool_input", {}))[:200],
"has_error": "error" in str(tool_response).lower(),
}
audit_log.append(log_entry)
# 如果工具报错了,给 AI 额外提示
if log_entry["has_error"]:
return {
"hookSpecificOutput": {
"hookEventName": "PostToolUse",
"additionalContext": "上一个命令出错了,请检查命令是否正确。",
}
}
return {}
紧急停止
PostToolUse 还能让你在发现严重问题时紧急停止 AI:
async def emergency_stop_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""发现严重错误时停止执行"""
tool_response = str(input_data.get("tool_response", ""))
if "CRITICAL" in tool_response or "FATAL" in tool_response:
return {
"continue_": False, # 停止!
"stopReason": "检测到严重错误,紧急停止",
}
return {"continue_": True}
组合多个 Hook
实际使用中,你通常需要组合多个 Hook:
options = ClaudeAgentOptions(
allowed_tools=["Bash", "Read", "Write"],
hooks={
"PreToolUse": [
# 1. 安全拦截(所有工具)
HookMatcher(matcher=None, hooks=[safety_hook]),
# 2. 自动批准安全操作(所有工具)
HookMatcher(matcher=None, hooks=[auto_approve_safe_commands]),
],
"PostToolUse": [
# 3. 审计日志(所有工具)
HookMatcher(matcher=None, hooks=[audit_hook]),
# 4. 紧急停止(只监控 Bash)
HookMatcher(matcher="Bash", hooks=[emergency_stop_hook]),
],
}
)
执行顺序:
graph TD
A["AI 要用 Bash 执行 ls -la"] --> B["PreToolUse 阶段"]
B --> B1["safety_hook → 不在黑名单,放行"]
B --> B2["auto_approve_safe_commands → ls 是安全命令,自动批准"]
B2 --> C["执行 Bash(ls -la)"]
C --> D["PostToolUse 阶段"]
D --> D1["audit_hook → 记录到审计日志"]
D --> D2["emergency_stop_hook → 没有 CRITICAL,继续"]
Hook 的三种返回决策
# 1. 拦截(deny)
return {"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "原因"
}}
# 2. 批准(allow)
return {"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "原因"
}}
# 3. 不表态(交给默认机制)
return {}
本课小结
- Hook 是 AI 工具调用的"拦截器"
- PreToolUse:在工具执行前拦截危险操作、自动批准安全操作
- PostToolUse:审计日志、错误检测、紧急停止
- 多个 Hook 可以组合使用,按顺序执行
- 三种决策:deny(拦截)、allow(批准)、不表态(交给默认机制)
课后练习
- 把 safety_hook 跑起来,试试让 AI 执行
rm -rf /,看看是否被拦截 - 给 audit_hook 加上"把日志写到文件"的功能
- 写一个 Hook,限制 AI 只能操作特定目录(比如
/tmp/ai-workspace/)