通过 .claude/settings.json 权限配置与 pre-tool hook 实现命令级安全管控,防止 AI 执行 rm -rf 或 git push --force 等危险操作,附完整代码实现。
让 Claude Code 获得真正的自主权,同时避免它搞垮你的系统
作者:Harry Philippe Mbouyap。本文所有代码均以 MIT 协议开源,代码仓库可克隆运行:https://github.com/hbouyap/claude-code-safe-automation
让 AI Agent 写代码已经是一个被解决的问题。真正让团队夜不能寐的是另一件事:你敢不敢让它无需人工干预就在你的机器上执行命令?
一次在错误目录下执行 rm -rf,一次把 git push --force 推到了错误的分支,原本的「Agent 帮我省了一下午」就会变成「Agent 害我折腾了一整周」。所以大多数人会把 Agent 用绳子拴得死死的——每一步都要审批——这实际上把它的价值扔掉了大半。
有一种更好的中间路线。你可以让 Agent 在一个工作流上获得真正的自主权,同时在结构上让它无法做出那几件真正会造成伤害的事。下面是我在 Claude Code 上使用的三层方案。
首先在 .claude/settings.json 中声明 Agent 允许操作的范围:
{
"permissions": {
"allow": ["Bash(ls:*)", "Bash(git status:*)", "Bash(git diff:*)", "Read(*)", "Grep(*)"],
"deny": ["Bash(sudo:*)", "Read(.env)", "Read(**/secrets/**)"]
}
}
这只是必要条件,远非充分条件。权限的 glob 匹配是粗粒度的——Bash(git:*) 既允许 git status 也允许 git push --force。对于那些真正重要的命令,你需要的是逻辑判断,而不是 glob 匹配。这就是下一层要解决的问题。
Claude Code 可以在每次工具调用之前运行一个钩子。如果钩子以退出码 2 退出,调用会被拒绝,拒绝原因会返回给模型。这正是放置「绝不能无人值守运行」黑名单的完美位置:
import json, re, sys
DANGEROUS = [
# rm -rf with any flag order, plus PowerShell alias rm -Recurse -Force
(r"\brm\b(?=.*(?:-[a-z]*r|--recursive))(?=.*(?:-[a-z]*f|--force))", "recursive force delete"),
(r"\bremove-item\b(?=.*-rec)(?=.*-for)", "recursive force delete (Windows)"),
(r"\bgit\s+push\b(?=.*(?:--force\b|\s-f\b))", "force push can overwrite history"),
(r"\bgit\s+reset\s+--hard\b", "hard reset discards work"),
(r"\b(?:curl|wget)\b.*\|\s*(?:sudo\s+)?(?:sh|bash)\b", "pipe-to-shell from the network"),
# ... fork bombs, raw disk writes, sudo, format, shred, del /s, rmdir /s
]
def main():
event = json.load(sys.stdin)
if event.get("tool_name") != "Bash":
return 0
command = event.get("tool_input", {}).get("command", "")
for pattern, reason in DANGEROUS:
if re.search(pattern, command, re.IGNORECASE):
print(f"guard: blocked -- {reason}", file=sys.stderr)
return 2 # Claude Code treats exit 2 as "deny"
return 0
sys.exit(main())
有两个看起来不起眼但实际上至关重要的细节:
Windows 也要覆盖。 网上大多数防护脚本片段只封禁 rm -rf。如果你的团队在 Windows 上运行 Claude Code,你还需要封禁 Remove-Item -Recurse -Force、del /s、rmdir /s、format 及其同类——否则在你们一半的机器上这个防护栏就是形同虚设。
大小写不敏感且跨标志顺序匹配。 rm -rf、rm -R -f、rm --recursive --force 和 rm -Rf 危险性相同。上面这个双前瞻正则表达式捕获的是「有递归标志 AND 有强制标志」,而不受排列顺序影响,同时仍然允许普通的 rm file.txt 通过。
在 settings.json 中接入它:
{
"hooks": {
"PreToolUse": [
{ "matcher": "Bash",
"hooks": [ { "type": "command", "command": "python .claude/hooks/guard.py" } ] }
]
}
}
现在测试它——这是人们最容易跳过的一步:
echo '{"tool_name":"Bash","tool_input":{"command":"rm -rf /"}}' | python .claude/hooks/guard.py
# -> exit 2, blocked
echo '{"tool_name":"Bash","tool_input":{"command":"git status"}}' | python .claude/hooks/guard.py
# -> exit 0, allowed
黑名单与白名单
黑名单适合在自己的机器上进行交互式工作:封禁已知危险的,允许其他一切。对于无人值守运行或客户项目,切换成失败即关(fail-closed)模式:只有你明确允许的命令才能运行,其他一律拒绝。无论哪种方式,都记录每次决策,这样你就有了一份 Agent 尝试操作的审计轨迹。
Shell 不是 Agent 影响外部世界的唯一途径。一旦你给它一个与你的 API 或数据库通信的 MCP 服务器,同样的原则就必须适用。保你安全的规则是:
读取自由。写入需审批。
给 Agent 所需的全部读取权限,但让每次状态变更都需要第二个刻意的信号——或者直接拒绝。例如,一个只读的 SQLite 服务:
@mcp.tool()
def query(sql: str) -> str:
"""Run a SELECT query. Non-SELECT statements are refused."""
if not sql.lstrip().lower().startswith("select"):
return "refused: only SELECT statements are allowed (read-only guardrail)."
# ... open the DB with a read-only connection and run it
对于任何会变更状态的操作,要求一个显式的 confirm=true 参数,这样 Agent 就不会因为意外而改变东西:
@mcp.tool()
def update_resource(path: str, body: str, confirm: bool = False) -> str:
if not confirm:
return "refused: pass confirm=true to perform this write (guardrail)."
# ... perform the write
凭证始终存放在环境变量中,绝不放在代码或工具参数里——这样它们就不会泄露到转录记录或日志中。
三层各司其职:
范围限定权限 — 粗粒度的围栏。
PreToolUse 钩子 — 智能的 Shell 命令关卡,黑名单或失败即关的白名单,带审计日志。
加门控的 MCP 工具 — 读取免费,写入需确认,秘钥存在环境变量里。
有了这些措施,你就可以把 Claude Code 指向一个真实的工作流——写代码、测试、部署、验证——然后让它跑起来,因为那几件真正会造成伤害的操作在结构上已经被阻断了。
上述所有内容都在一个小的 MIT 协议仓库里,你可以在一分钟内克隆并运行:https://github.com/hbouyap/claude-code-safe-automation — 包含一个可工作的防护栏、范围限定权限、一个审查者子 Agent,以及一个最小的 MCP 服务器模板。
如果你不想自己组装,我还打包了一个更完整的 MCP & 防护套件(失败即关白名单模式、三个 MCP 服务器模板、一个子 Agent 库,以及一键安装脚本)——我也直接为团队构建这类环境。链接在我的个人主页上。
你有哪些安全地给 Agent 授权的方法?欢迎分享。