Claude Code 安全实战:用 Hooks、权限、Git Worktrees 防误删(2026)

Claude Code 安全实战:用 Hooks、权限、Git Worktrees 防误删(2026)

TL;DR — Claude Code 出过真实的 rm -rf 数据丢失事故(2025-10 Mike Wolak 案,全机用户文件被删,他甚至没开 --dangerously-skip-permissions)。本文给出三层防御的最小可用配置:settings.json 里的 permissions.deny 兜命令前缀、PreToolUse hook 用正则二次过滤、Git worktrees 给每个 session 独立工作区。三层都加上,才算把”AI 编辑器”变成你能放心在生产分支上跑的工具。

为什么 Claude Code 需要专门的安全配置

Claude Code 默认配置很乐观。Edit、Write、Bash 这些破坏性工具,开箱就在 ask 列表里——意味着你会一直被弹窗烦,烦到大多数人最终会点开 --dangerously-skip-permissions(社区俗称 “Safe YOLO mode”),然后再也不烦了。

代价是这些:

  • 2025-10 Mike Wolak 案:他让 Claude Code 从一份干净的 checkout 重建 Makefile 项目,结果模型决定先 rm -rf 一个它认为是临时文件的路径,递归一路上溯到 /。GitHub bug report 的日志里有几千行 “Permission denied for /bin、/boot、/etc”。他没有--dangerously-skip-permissions——是权限系统本身没拦住。
  • eesel AI 的统计:在用 --dangerously-skip-permissions 的开发者里,32% 遇到过非预期的文件修改,9% 报告了真实的数据丢失或损坏。
  • Anthropic 自己的研究员 Nicholas Carlini 在 2026-02 跑 16 个并行 Claude Code agent 用 Rust 写一个能编译 Linux 内核的 C 编译器时,专门在博客里加了一句括号注释:“Run this in a container, not your actual machine.”

权限弹窗的疲劳是真实问题。但解法不是关掉它,是把”该挡的硬挡死、该放的全自动放过”——这正是 deny 规则 + hooks 要做的事。

第一道墙:settings.json 权限规则

Claude Code 在 settings.json 里有一个 permissions 对象,三个数组:allow / deny / ask。评估顺序是 deny → ask → allow,第一个匹配的规则赢——所以 deny 永远盖过其他两个。

关键permissions 是由 Claude Code 本体强制的,不是模型决定的。CLAUDE.md 里写”不要 rm -rf”完全没用,模型记不住也可以选择忽略;写在 settings.json 的 deny 里才是硬约束。

最小可用 deny 列表(放在项目根的 .claude/settings.json):

{
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Bash(rm -fr *)",
      "Bash(sudo *)",
      "Bash(chmod 777 *)",
      "Bash(curl * | sh)",
      "Bash(curl * | bash)",
      "Bash(git push --force *)",
      "Bash(git push -f *)",
      "Bash(git reset --hard *)",
      "Bash(git clean -fd*)",
      "Write(.env)",
      "Write(.env.*)",
      "Edit(.env)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npm publish *)"
    ],
    "allow": [
      "Bash(npm test*)",
      "Bash(pnpm test*)",
      "Read",
      "Glob",
      "Grep"
    ]
  }
}

三档分工:

  • deny 是绝对禁止线。rm -rfsudocurl | sh(远程执行未审计代码的最常见路径)、操作 .env 之类的凭证文件,统统拒。
  • ask 留给”我自己也想看一眼再决定”的命令,比如 push、publish。
  • allow 把测试、读文件这些纯增量、纯读取的操作全自动放过,把弹窗预算留给真正重要的事。

配置好后用 /permissions 命令在 Claude Code 里检查规则加载情况:它会列出所有规则以及来源的 settings.json 路径。

配置文件优先级(从下往上覆盖):

文件作用范围是否进 Git
~/.claude/settings.json全局所有项目否(个人配置)
.claude/settings.json单个项目是(团队共享)
.claude/settings.local.json单个项目否(gitignored)

实战建议:deny 列表写在项目级 .claude/settings.json 里 commit 进仓库,让全团队共享;个人偏好(比如某些命令你想 ask 而别人想 allow)放 settings.local.json

deny 规则的盲区

deny 规则只匹配你写出来的字面命令前缀。模型很容易绕:

# 这个会被 Bash(rm -rf *) 挡住
rm -rf /tmp/cache

# 但这个不会
sh -c "rm -rf /tmp/cache"

# 这个也不会
RM=rm; $RM -rf /tmp/cache

# 写进脚本再执行的,更不会
echo "rm -rf /tmp/cache" > /tmp/x.sh && bash /tmp/x.sh

deny 是第一道粗滤网,挡 80% 的常规 footgun。剩下的留给第二道墙。

第二道墙:PreToolUse Hook 主动拦截

Hook 是 Claude Code 的生命周期回调。每次工具调用前后,Claude Code 会把一个 JSON 上下文 pipe 给你的脚本,由脚本决定放行还是拦截。PreToolUse 是其中最关键的——它在工具真正执行前触发。

PreToolUse 拿到的 JSON 长这样(Bash 工具示例):

{
  "session_id": "abc123",
  "cwd": "/Users/marvin/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": {
    "command": "rm -rf /tmp/some/path",
    "description": "Clean up cache",
    "timeout": 120000
  },
  "tool_use_id": "unique-id"
}

你可以用任何能读 stdin + 写 stdout/stderr 的脚本处理它。退出码决定行为:

退出码行为
0放行(可在 stdout 输出 JSON 补充指令)
1不阻塞——命令照样执行,stderr 只进 debug 日志
2阻塞——工具调用被取消,stderr 反馈给模型

反直觉的坑:Unix 惯例里 exit 1 是失败,但 Claude Code 的 hook 协议把 1 当成”非致命警告”。要 block,必须 exit 2。这是社区最常见的 hook 失效原因。

下面是一个真正能拦住 rm -rf / 类灾难命令的脚本:

#!/bin/bash
# .claude/hooks/guard-bash.sh
COMMAND=$(jq -r '.tool_input.command')

# 危险命令正则集合
DANGER='(^|[[:space:];&|])rm[[:space:]]+(-[rRf]+[[:space:]]+)?(/|~|\$HOME)([[:space:]]|/|$)'

if echo "$COMMAND" | grep -qE "$DANGER"; then
  jq -n --arg cmd "$COMMAND" '{
    hookSpecificOutput: {
      hookEventName: "PreToolUse",
      permissionDecision: "deny",
      permissionDecisionReason: ("拦截:命令试图删除 / 或 ~:" + $cmd)
    }
  }'
  exit 2
fi

exit 0

注册到 .claude/settings.json

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh"
          }
        ]
      }
    ]
  }
}

记得 chmod +x .claude/hooks/guard-bash.sh

matcher 字段支持精确匹配(Bash)、多选(Edit|Write)、正则(^Notebook)、MCP 通配(mcp__memory__.*)。

Hook 能做的其他事

  • PostToolUse:工具执行之后触发,能拿到 tool_response。常见用法:每次 Edit/Write 之后自动跑 prettier、eslint,把 fix 结果通过 stdout 反馈给模型。
  • UserPromptSubmit:用户回车之前拦一道。可以做敏感关键词检测(“线上数据库”、“prod 表”),让模型先确认。
  • SessionStart:在会话开始时注入额外上下文(比如最近的 commit log、当前分支)。

Hook 的官方事件列表挺长——session、turn、tool、subagent、worktree 都有钩子。完整清单看 Anthropic 官方文档

第三道墙:Git Worktrees 隔离工作区

前两道墙拦的是”模型干了不该干的事”。worktree 拦的是另一类问题:多个 Claude session 互相覆盖文件

git worktree 是 Git 本身的功能——允许同一个仓库 checkout 到多个不同的工作目录,各自指向不同分支,共享同一份对象库。Claude Code 官方推荐用它跑并行 session:

# 假设你的主仓库在 ~/myproject
cd ~/myproject

# 给三个并行任务各开一个 worktree
git worktree add ../myproject-feature-auth feature/auth
git worktree add ../myproject-bugfix-cors bugfix/cors
git worktree add ../myproject-refactor-db refactor/db

# 在每个目录开一个 Claude Code session
cd ../myproject-feature-auth && claude
# 另一个 terminal
cd ../myproject-bugfix-cors && claude

每个 session 看到的文件状态完全独立——A session 改的文件不会出现在 B session 的 Read 结果里,更不会被 B session 的 Edit 覆盖。Git 历史和 remote 还是共享的,merge / rebase 照常。

实际限制

  • 主流团队(截至 2026 年中)在用 4–8 个并行 worktree 每开发者。超过这个数,瓶颈通常不是 Claude,而是你 review 不过来。
  • Anthropic 自己的建议是 2–4 个并行 session 起步。
  • 大仓库上 5 个以上 session 会撞 API 速率限制,并且 review 体验会塌。

worktree 不解决的事

worktree 只隔离文件系统。下面这些是共享的:

  • 环境变量:所有 session 读同一份 shell env。一个 session export DATABASE_URL=... 会污染其他 session。
  • 本地数据库:两个 session 跑 migration 会互相踩,写测试数据会污染对方的 fixtures。这是实践里最容易翻车的点。
  • 网络端口:两个 session 都 npm run dev 抢 3000 端口,第二个起不来。
  • 全局 npm/pnpm cache:装包时可能互相覆盖 lockfile。

要彻底隔离,要么每个 worktree 配独立的 .env 指向独立的数据库实例(推荐 Docker compose 给每个 worktree 起一份),要么把每个 session 跑在容器里(社区在用 Devcontainer + Claude Code 的方案)。

auto mode:比 --dangerously-skip-permissions 更安全的 YOLO

Anthropic 2026 推出了 auto mode 作为 --dangerously-skip-permissions 的替代。它跳过权限询问让 Claude 一路跑到底,但仍然受 deny 规则约束——这意味着即使在”一路自动”模式下,你写的 Bash(rm -rf *) deny 规则照样会拦住模型。

实战推荐:

  • 日常开发:默认模式,深 deny + 几条 ask,依靠 hook 兜底。
  • 长任务(比如让 Claude 跑一晚上重构):auto mode + 完整 deny 列表 + hook,外加跑在容器里。
  • 真正需要无人值守的探索性任务:--dangerously-skip-permissions + 容器 + 容器里没挂宿主机敏感目录。

不要直接在生产代码所在的工作目录上开 --dangerously-skip-permissions

三层防御的组合套路

把上面三件事拧成一个最小可用模板:

项目根 .claude/settings.json

{
  "permissions": {
    "deny": [
      "Bash(rm -rf *)",
      "Bash(sudo *)",
      "Bash(curl * | sh)",
      "Bash(curl * | bash)",
      "Bash(git push --force *)",
      "Bash(git reset --hard *)",
      "Write(.env)",
      "Write(.env.*)"
    ],
    "ask": ["Bash(git push *)"],
    "allow": ["Read", "Glob", "Grep", "Bash(pnpm test*)", "Bash(npm test*)"]
  },
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard-bash.sh"
          }
        ]
      }
    ]
  }
}

项目根 .claude/hooks/guard-bash.sh(前面给的脚本)。

工作流:每个独立任务开一个 worktree(git worktree add ../proj-<task> <branch>),在 worktree 里启动 Claude Code,长任务加 auto mode。

这套配置不会让 Claude Code 慢下来——allow 列表把大部分高频操作直接放过;deny 列表是 O(1) 字符串匹配;hook 只在 Bash 调用时跑一次几毫秒的脚本。但它会真的拦住模型脑抽时干出来的破坏性操作。

国内开发者:用 ofox API 跑 Claude Code

Claude Code 默认连 Anthropic 官方 API,国内网络不稳。把 ANTHROPIC_BASE_URL 指向 ofox 后即可在国内直连,所有 hooks、permissions、worktrees 配置完全不受影响——它们都在客户端。详细的国内接入步骤、模型可用性和定价对比,看 Claude Code 国内使用 + Opus 4.6 编程体验Claude Opus 4.7 完全指南

横向对比 Claude Code 与 Cursor、Windsurf 等同类工具的取舍,参考 Vibe Coding 工具横评

总结

权限弹窗烦人是真的,但解法不是关掉它。三层加起来才稳:

  1. settings.json 权限规则——粗滤网,挡 80% 的常规误操作。deny 永远赢,写进项目根 commit 进 Git。
  2. PreToolUse hook——细滤网,用脚本做正则匹配兜住 deny 漏掉的变种。记住用 exit 2 才能 block。
  3. Git worktrees——并行 session 不互相踩文件,但环境变量/数据库要单独想办法。

配完之后你才能在生产分支上放心开 Claude Code,而不是开完一晚上心惊胆战地查 git status