第 6 课:Hook 与安全管控
为什么要管 AI?
你的私人助理能操作文件、能执行命令。如果它误删了你的代码怎么办?
你: 帮我清理一下临时文件
AI: [执行 rm -rf /tmp/*] ← 万一删错了呢?
AI: [执行 rm -rf ~/Documents] ← 万一理解错了呢?
Hook 就是"安全围栏"——在 AI 动手之前先检查,动手之后再审计。
Hook 的工作原理
AI 想执行操作
│
├── ① PreToolUse Hook → 执行前检查
│ ├── allow → 放行
│ ├── deny → 拦截
│ └── (空) → 走默认流程
│
├── ② 工具实际执行
│
└── ③ PostToolUse Hook → 执行后处理
├── 记录日志
├── 检查结果
└── 给 AI 追加提示
场景一:文件保护
from claude_agent_sdk.types import HookInput, HookContext, HookJSONOutput
# 受保护的目录
PROTECTED_PATHS = [
"~/.ssh",
"~/.config",
"~/.env",
".git",
"node_modules",
"__pycache__",
]
async def file_protection_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""保护重要文件和目录"""
tool_name = input_data["tool_name"]
tool_input = input_data.get("tool_input", {})
# 检查写操作
if tool_name in ["Write", "Edit", "Bash"]:
# 获取目标路径
target = ""
if tool_name in ["Write", "Edit"]:
target = tool_input.get("file_path", "")
elif tool_name == "Bash":
command = tool_input.get("command", "")
# 检查危险命令
dangerous = ["rm -rf", "rm -r", "rmdir", "mv /", "chmod -R"]
for d in dangerous:
if d in command:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"🛡️ 拦截危险命令: {d}",
}
}
# 检查受保护路径
for protected in PROTECTED_PATHS:
if protected in target:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": f"🛡️ 受保护目录: {protected}",
}
}
return {}
场景二:操作审计日志
import datetime
audit_log: list[dict] = []
async def audit_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""记录所有操作"""
entry = {
"time": datetime.datetime.now().strftime("%H:%M:%S"),
"tool": input_data.get("tool_name", "?"),
"input": str(input_data.get("tool_input", ""))[:150],
}
audit_log.append(entry)
return {}
def show_audit_log():
"""显示审计日志"""
if not audit_log:
print("📋 暂无操作记录")
return
print(f"📋 操作日志({len(audit_log)} 条):")
for e in audit_log[-10:]:
print(f" [{e['time']}] {e['tool']}: {e['input'][:80]}")
场景三:安全命令白名单
SAFE_COMMANDS = [
"ls", "pwd", "echo", "cat", "head", "tail",
"wc", "grep", "find", "which",
"python --version", "git status", "git log",
"date", "cal",
]
async def command_whitelist_hook(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""Bash 命令白名单:安全命令自动放行"""
if input_data["tool_name"] != "Bash":
return {}
command = input_data.get("tool_input", {}).get("command", "").strip()
for safe in SAFE_COMMANDS:
if command.startswith(safe):
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": f"✅ 安全命令: {safe}",
}
}
# 不在白名单里 → 走默认权限流程(会提示用户确认)
return {}
场景四:自动批准只读操作
async def auto_approve_readonly(
input_data: HookInput,
tool_use_id: str | None,
context: HookContext
) -> HookJSONOutput:
"""自动批准只读工具"""
readonly_tools = ["Read", "Grep", "Glob",
"mcp__productivity__list_todos",
"mcp__productivity__search_notes",
"mcp__productivity__list_recent_notes",
"mcp__productivity__list_files"]
if input_data["tool_name"] in readonly_tools:
return {
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "allow",
"permissionDecisionReason": "只读操作,自动批准",
}
}
return {}
场景五:权限回调
Hook 是全局拦截,权限回调(Permission Callback)是逐工具审批:
from claude_agent_sdk import (
PermissionResultAllow, PermissionResultDeny,
ToolPermissionContext,
)
async def productivity_permissions(
tool_name: str,
input_data: dict,
context: ToolPermissionContext
) -> PermissionResultAllow | PermissionResultDeny:
"""生产力工具权限控制"""
# 查看类:自动允许
if "list" in tool_name or "search" in tool_name or "get" in tool_name:
return PermissionResultAllow()
# 添加类:允许
if "add" in tool_name or "create" in tool_name:
return PermissionResultAllow()
# 删除类:需要确认(这里示例打印确认)
if "delete" in tool_name:
target = str(input_data)[:100]
print(f"\n⚠️ 删除操作: {tool_name}")
print(f" 目标: {target}")
confirm = input(" 确认?(y/N): ").strip().lower()
if confirm == 'y':
return PermissionResultAllow()
return PermissionResultDeny(message="用户取消了删除操作")
# 其他:允许
return PermissionResultAllow()
组装安全层
from claude_agent_sdk.types import HookMatcher
options = ClaudeAgentOptions(
system_prompt="你是 MiniClaw 私人助理...",
mcp_servers={"productivity": productivity_tools},
allowed_tools=[...],
# Hook 配置
hooks={
"PreToolUse": [
# 文件保护(最高优先级)
HookMatcher(matcher="Bash", hooks=[file_protection_hook]),
HookMatcher(matcher="Write", hooks=[file_protection_hook]),
HookMatcher(matcher="Edit", hooks=[file_protection_hook]),
# 自动批准只读
HookMatcher(matcher=None, hooks=[auto_approve_readonly]),
# Bash 白名单
HookMatcher(matcher="Bash", hooks=[command_whitelist_hook]),
],
"PostToolUse": [
# 审计日志(记录所有操作)
HookMatcher(matcher=None, hooks=[audit_hook]),
],
},
# 权限回调
can_use_tool=productivity_permissions,
)
Hook 和 Permission Callback 的分工
PreToolUse Hook: Permission Callback:
├── 文件保护(拦截危险路径) ├── 查看 → 自动允许
├── 命令白名单(放行安全命令) ├── 添加 → 自动允许
└── 只读自动批准 └── 删除 → 需要确认
PostToolUse Hook:
└── 审计日志(记录所有操作)
简单说:Hook 管"不能做什么",Callback 管"怎么做"。
本课小结
- Hook 在工具执行前后拦截,做安全检查
- PreToolUse:文件保护、命令白名单、只读自动批准
- PostToolUse:审计日志、结果检查
- Permission Callback:逐工具精细控制
- 多层防御:Hook + Callback + 工具内部检查
课后练习
- 实现一个"操作频率限制"Hook:同一工具 1 分钟内最多调 10 次
- 把审计日志保存到文件,每天一个文件
- 实现"撤销"功能:记录最近的写操作,支持回退