用工具调用钩子拦截 Agent 身份泄露
文章展示通过 Claude Code 的 PreToolUse 钩子检查工具调用,降低 Agent 发帖、填表时泄露账户持有人身份的风险。钩子读取调用 JSON,以退出码决定放行或阻断,并强调敏感信息不应写入脚本。
AI 深度解读
把「别泄露账号持有人的身份」写进 CLAUDE.md,能提醒 AI,却不能保证每次都守住。原文以 Claude 的第一人称,介绍了一套约 60 行的 Claude Code hook:在工具执行前扫描敏感内容,命中规则就拦截,让模型改写后再试。
它值得看的地方,是把隐私要求变成了程序检查,同时交代了检查的边界。对于已经让 AI 发帖、调用网络工具、填写表单的人,这比再加一句提示词更有参考价值。但文中的设计目标,比示例代码实际覆盖的范围更大。
为什么不能只靠 CLAUDE.md 约束
原文里的 Claude 拥有邮箱、商店账号和社交账号,可以自行发帖、填表,不必每次请示。背后的账号持有人承担责任,要求则是保护其身份。
风险不一定来自模型故意违背要求。复制日志、拿错表单字段,都可能把私人信息带出去。
作者因此使用 Claude Code 的 PreToolUse hook。它在工具调用前运行,从标准输入读取 JSON:退出码为 0 时放行,为 2 时阻止调用,并把标准错误输出交给模型。
需要每次成立的规则,应该有独立于模型记忆的检查。
下面的配置将检查挂到所有工具调用前:
{
"hooks": {
"PreToolUse": [
{ "matcher": "*",
"hooks": [{ "type": "command", "command": "python3 ~/.claude/hooks/identity-guard.py" }] }
]
}
}
要看的是 "matcher": "*":先让所有工具经过入口,再由脚本区分哪些可以跳过。
为什么姓氏要严查,名字却只查外发
作者把规则分成两档。
姓氏、邮箱等辨识度高的信息,除纯读取工具外一律检查。常见名字容易与普通文字、代码冲突,所以只在可能外发的路径检查。
脚本将 Read、Glob、Grep 归为纯读取,将 WebSearch、WebFetch 放进 PUBLISH_TOOLS,还检查所有以 mcp__ 开头的工具,以及包含 curl、wget、git push、playwright、ssh、scp、rsync 的 Bash 命令。
敏感规则独立存放在仓库之外,目录权限设为 700。代码实际读取 strict.txt 和 outbound.txt:前者可写 someone@example\.com,后者可写 \bFirstname\b。加载时使用 re.I,忽略大小写。原文前面还提到 patterns.txt,与后续代码命名不一致,落地时应以实现为准。
另一个关键点是读取文件内容。发布工具可能只接收文件路径,光检查参数,看不到真正发出去的正文。以下是脚本的检查片段:
text = json.dumps(inp)
p = inp.get("file_path")
if tool in PUBLISH_TOOLS and p and Path(p).is_file(): # scan what is actually sent
text += Path(p).read_text(errors="ignore")
if any(pat.search(text) for pat in load("strict.txt")):
block(f"{tool} input contains a private string.")
outbound = (tool in PUBLISH_TOOLS or tool.startswith("mcp__")
or (tool == "Bash" and OUTBOUND_BASH.search(inp.get("command", ""))))
if outbound and any(pat.search(text) for pat in load("outbound.txt")):
block(f"{tool} input could leave this machine and contains a private word.")
要盯住 tool in PUBLISH_TOOLS 这一行:文件正文扫描只对这个集合里的工具生效。实际发布工具必须补进去;仅仅被认定为外发的 MCP 工具,并不会自动进入这条文件读取分支。
一次正确拦截,证明不了所有出口都安全
作者报告了一次实际拦截:为了展示脱敏后的脚本,Claude 写了条 sed 替换命令,但命令里包含待替换的私人字符串,因此被阻止。
这符合规则。检查器只判断敏感内容有没有出现,不推断模型是在脱敏还是准备泄露。作者还发现,拦截消息写清原因和修改方向后,模型能够改写并继续;只有退出码 2,则需要猜哪里出了问题。
作者在自己的会话中修改 settings.json 后,无须重启就生效,并用无害的 echo 验证过。这个观察不应扩展为所有环境的保证,配置后仍应现场测试。
更大的盲区在调用内部:执行 python fill.py 时,hook 看到的是命令,看不到脚本随后往浏览器里输入的值;网页返回内容也不在这段检查的覆盖范围内。作者因此仍会在提交表单前目视检查字段。
此外,脚本异常时打印错误后放行。作者担心配置拼错会让全部工具停摆,接受了这个取舍;风险更高时,可以改为出错即阻断。
我的判断
我认同把明确的敏感字符串交给确定性检查。它能解释、能复现,也能给模型明确的修改反馈,比单独依赖提示词更适合承担这类约束。
但原文没有给出漏报率、误报率或替代方案的对照数据。一次 sed 拦截说明该路径有效,不能证明身份保护完整可靠。
示例还有几处需要补齐:文字提到检查发布目录里的文件,代码没有实现这部分;Bash 外发判断依赖命令文本正则,也不是对网络行为的完整识别。代理可以先读取本地信息,再由已有脚本发送,而这段 hook 未必能看到实际载荷。
这是一道有用的检查关口,覆盖范围需要逐条验证。
相比只写 CLAUDE.md,它增加了强制执行;相比在实际发送位置检查内容,它更容易接入,但更依赖工具名称、参数格式和命令写法。该选哪一层,取决于真正需要守住的出口。
能拿来做什么
- 给日常 AI 编程环境加入身份检查时,先把邮箱、姓氏等明确规则放到仓库外,并使用权限为
700的目录保存,避免保护规则自身进入版本库。 - 如果让 AI 调用发布脚本或 MCP 工具,先核对工具名称和文件参数,把真正发送的正文接入检查,不能只确认文件名经过扫描。
- 配置完成后,用无害的
echo触发拦截,再测试日志粘贴、文件发布和curl表单提交,确认每条路径都返回清楚的纠正提示。 - 对已有的浏览器填表脚本,保留提交前字段核对,并按风险选择异常时放行还是阻断;不要仅凭 hook 已启用就取消检查。