Skip to content

Claude Code 权限加固:禁止写入仓库之外、权限提示直接拒绝、审计 settings.local.json

更新于

一份在 Claude Code 2.1.319 上实测过的 settings.json:blockWritesOutsideWorkingDirectories、denyInsteadOfPrompting、Read deny 规则,以及无人值守运行用的 dontAsk 模式,另附 settings.local.json 泄露审计和后门扩展检查。

先说结论: 把下面这段放进仓库根目录的 .claude/settings.json,提交到 git,然后重启 Claude Code。需要 Claude Code 2.1.319 或更高版本(先 claude --version 看一下,再 claude update)。

{
  "permissions": {
    "blockWritesOutsideWorkingDirectories": true,
    "denyInsteadOfPrompting": true,
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Bash(curl *)",
      "Bash(wget *)"
    ]
  }
}

每一行都在换什么:

配置项作用引入版本
blockWritesOutsideWorkingDirectoriesWrite、Edit、NotebookEdit 三个工具拒绝工作目录之外的任何路径,在所有权限模式下生效,包括 bypassPermissions2.1.314(2026 年 10 月 30 日)
denyInsteadOfPrompting本来会卡在权限提示上的工具调用直接被拒绝,本轮对话继续往下走;对终端会话、子代理和 teammates 都生效2.1.319(2026 年 11 月 6 日)
Read(...) deny 规则.env 和 secrets/ 对文件工具不可读,对 cat、head、sed 这类点名文件的 shell 命令同样不可读长期存在
Bash(curl *) / Bash(wget *)shell 发起的网络请求直接拒绝,而不是弹出询问长期存在

写入拦截、deny 规则和无人值守运行的行为,我于 2026 年 11 月 8 日在 macOS 上用 Claude Code 2.1.319 实测过,方式是以 claude -p --output-format json 运行,每项测试单独指定权限模式,下文会原样引用拒绝时的提示信息。更新日志 (opens in a new tab)把 denyInsteadOfPrompting 描述为作用于终端会话、子代理和 teammates,而 -p 运行本来就会拒绝这些调用,所以它的描述来自更新日志,不是实测结果。本文剩余部分解释为什么现在就要上这些配置、每一项覆盖什么和不覆盖什么,以及三项建议只跑一次的检查。

为什么是现在:九天内三起安全事件

2026 年 10 月 26 日到 11 月 6 日之间 Claude Code 发了七个版本,同一窗口内出了三起安全事件。每一起都对应一项具体的检查。

日期发生了什么你要做的一次性检查
10 月 30 日Phorion 在 Cursor Marketplace 上发现了官方 Claude Code for VS Code 扩展的后门版本,版本号 2.1.312 和 2.1.313。它会往 ~/.ssh/authorized_keys 追加攻击者的 SSH 公钥并安装持久化(通告 (opens in a new tab),HN 1,011 分 (opens in a new tab))列出你的扩展版本并 grep authorized_keys,见下文第 5 步
11 月 5 日GitHub issue #107757 (opens in a new tab):Claude Code 把账号邮箱放进了网页请求的 User-Agent 头。近 1,000 个 reaction,11 月 8 日仍未关闭(HN 1,597 分 (opens in a new tab))升级到 2.1.317 或更高版本,它会在工具发送你的邮箱之前先询问,见下文第 6 步
11 月 7 日Apiiro 在 GitHub 公开仓库里找到 1,122 个文件:用户点了「Yes, and don't ask again」之后,一条带密码或 token 的 shell 命令被保存进了 .claude/settings.local.json(报告 (opens in a new tab),HN 500+ 分 (opens in a new tab))跑一遍下文第 4 步的审计脚本

这三件事没有一件能靠切换权限模式解决。需要的是一份配置文件、一次审计和一次版本检查。

热门文章

第 1 步:禁止在仓库之外写文件

permissions.blockWritesOutsideWorkingDirectories 让三个文件编辑工具拒绝任何不在工作目录内的路径。工作目录包括你启动 Claude Code 时所在的文件夹,以及通过 /add-dir、--add-dir 或用户级 / 托管(managed)settings 里的 permissions.additionalDirectories 加进来的目录。写在仓库自己的 .claude/settings.json 里的 additionalDirectories 条目不会解除这个拦截。这个拦截在所有权限模式下都生效,包括 bypassPermissions;而且任何一个 settings 文件里的 true 都会压过其他地方的 false,所以仓库可以为每个 clone 它的人统一开启。

我在一个临时项目里设了这个键,然后在 acceptEdits 模式下让 Claude Code 用 Write 工具在我的 home 目录创建一个文件。调用被拒绝,提示如下:

/Users/me/kanaries-block-test.txt is outside /path/to/proj; the
permissions.blockWritesOutsideWorkingDirectories setting blocks writes
outside the working directories. Ask the user to add the directory with
/add-dir, or to remove that setting.

文件没有被创建,JSON 结果里的 permission_denials 记录了这次调用,tool_name: "Write"。

依赖它之前要知道四个边界:

  • 它不限制 shell 命令、MCP 工具和 hook 程序。 从 Bash 工具里执行 echo x > ~/file 不是一次 Write 调用。要圈住 shell 的写操作,得开沙箱,见下文「可选:把 shell 也关进沙箱」。
  • Claude Code 自己的一部分文件仍然可写:plan 文件、会话 scratchpad、后台会话的 tmp 目录,以及项目的 auto-memory .md 文件。这是有意的设计。
  • Claude 再也不能编辑任何 Claude Code settings 文件,包括仓库内部的,因为一个 settings 文件就可能解除这个拦截。.claude/settings.json 请你自己动手改。
  • worktree 工具会被移除。 开启这个键后,Claude Code 会移除 EnterWorktree 和 ExitWorktree,或者拒绝对它们的调用。如果子代理需要 worktree,就把仓库根目录下的 .claude/worktrees 加为工作目录。

它的姊妹配置 permissions.blockReadsOutsideWorkingDirectories(2.1.257 或更高版本)对 Read、Grep、Glob 和 LSP 调用做同样的事。如果这个仓库不该看到你磁盘上的其他内容,就把它也打开。

第 2 步:用 deny 规则让密钥不可读

一条 Read(path) deny 规则会拦住内置文件工具,并且按文档说法,也会拦住 Claude Code 在 Bash 里能识别的文件命令(cat、head、tail、sed、tee)以及点名该文件的 shell 重定向。它同时拦截同一路径上的 Edit 和 Write,所以 Claude 也没法覆盖你的 .env。

实测:配置 "deny": ["Read(./.env)"] 后,让 Claude 用 Read 工具读 .env,返回:

File is in a directory that is denied by your permission settings.

这条规则同样管到 shell。在一次 acceptEdits 运行里,我先要求执行 cat README.md,再执行 cat .env:前者正常打印了文件,后者被拒绝,提示 Permission to use Bash with command cat .env has been denied.。另一次默认模式的 claude -p 运行中,curl -sI https://example.com 因为 Bash(curl *) 规则被拒绝。每一次拒绝都记录在 permission_denials 里,每次运行都正常结束,没有挂住。

来自权限参考文档 (opens in a new tab)的三个规则语法坑:

  • 用 Read(...) 和 Edit(...)。带路径的 Write(docs/**)、Grep(docs/**)、Glob(...) 规则会被接受,但永远不会匹配到任何东西。
  • Bash(git diff *) 在 * 前面有空格,匹配所有以 git diff 开头的命令。Bash(git diff*) 则会连 git diff-index 也匹配进去。
  • deny 规则抓不住那些不点名文件就把文件读出来的命令,比如在存放密钥的目录下执行 grep -r pattern .。这种情况要靠沙箱。

.claudeignore 文件没有任何效果。把里面的条目挪到 Read deny 规则里。

第 3 步:绝不卡在权限提示上

现在有四种办法让无人值守的 Claude Code 运行遇到权限提示时直接拒绝而不是干等。按运行发生的场景来选。

场景用什么行为
你走开了的交互式终端会话,包括它的子代理和 teammatessettings 里设 "denyInsteadOfPrompting": true(2.1.319)本来会等待提示的调用直接被拒绝,本轮继续执行(更新日志原话;我没法在 -p 模式下测它,因为 -p 本来就会拒绝)
没有指定权限模式的 claude -p 或 Agent SDK 运行什么都不用加从 2.1.302 起,-p 以 Manual 模式启动,拒绝任何需要审批的调用。上文已实测
严格受限的 CI 任务--permission-mode dontAsk所有会弹提示的调用一律拒绝(你安装的 mod 仍可能放行其中一部分)。只读命令、工作目录内的文件读取,以及你的 allow 规则照常运行
带权限宿主的运行,比如用了 canUseTool 或 --permission-prompt-tool 的 SDK 应用--permission-prompts none(2.1.259 或更高版本)不再等待宿主响应;Claude 会被告知没有人能审批,并且不要重试

用 --permission-mode dontAsk --permission-prompts none 实测:python3 -c 'print(2+2)' 被拒绝,提示以 Permission to use Bash has been denied because Claude Code is running in don't ask mode 开头;而 git log --oneline -1 正常执行,因为它在内置的只读命令清单上。结果 JSON 的 permission_denials 里有一条记录。

有些东西任何模式、任何 allow 规则都批不了,连 bypassPermissions 也不行:显式的 ask 规则、AskUserQuestion 工具、标记为 requiresUserInteraction 的 MCP 工具、以及被识别为关键路径的 rm 命令(比如 rm -rf ~)。dontAsk 会把这些全部直接拒绝,并且即使有 allow 规则匹配,也会拒绝对网络路径的读取。

如果某个仓库永远不该在 bypass 模式下运行,就在 permissions 下加 "disableBypassPermissionsMode": "disable"。之后 Claude Code 会拒绝 --dangerously-skip-permissions,并忽略子代理定义里要求 bypass 的声明。

第 4 步:审计 settings.local.json 里泄露的 allow 规则

先说 Apiiro 报告背后的机制。当 Claude 请求执行一条 shell 命令、你选了「Yes, and don't ask again」时,Claude Code 会把带有该命令前缀的 allow 规则保存到仓库根目录的 .claude/settings.local.json。如果那条命令是 psql postgres://app:Sup3rSecret@db/prod -c ...,密码现在就躺在规则里了。

Claude Code 第一次在一个尚未忽略该文件的仓库里写这个文件时,会把 **/.claude/settings.local.json 加进你的全局 git excludes 文件。在我的 Mac 上,这个文件是 ~/.config/git/ignore,那条规则在第 1 行。这就是为什么在这里执行 git add .claude/settings.local.json 会报 "ignored by one of your .gitignore files"。但这层保护是按机器算的,不是按仓库算的。在 Claude Code 写入之前你手动创建的文件、以目录拷贝而非 clone 方式搬到另一台机器的仓库、没有你全局 excludes 的沙箱环境,都会把这个文件当普通文件一样提交。Apiiro 的作者在 HN 上说,在追溯到的 1,105 个仓库中,有 539 个的该文件改动来自 Claude 署名为 co-author 的提交。

在仓库根目录跑下面的脚本。它会找出所有 .claude/settings*.json,报告 git 是否在跟踪它,并标出带凭据或放行整个工具族的 allow 规则。

cat > /tmp/claude-settings-audit.sh <<'EOF'
#!/usr/bin/env bash
set -u
root="${1:-.}"
found=0
while IFS= read -r -d '' f; do
  found=1
  echo "== $f"
  if git -C "$(dirname "$f")" ls-files --error-unmatch "$(basename "$f")" >/dev/null 2>&1; then
    echo "   TRACKED BY GIT  <- settings.local.json should never be"
  fi
  grep -nE '"(Bash|PowerShell)\([^"]*(password|passwd|token|secret|api[_-]?key|Authorization|Bearer |PGPASSWORD|AWS_SECRET|--password|://[^"/]*:[^"@]*@|[A-Z_]{3,}=[^[:space:]"]{8,})' "$f" \
    | sed 's/^/   SECRET-LIKE: /'
  grep -nE '"(Bash|PowerShell)(\(\*\))?"|"Bash\((sudo|rm|curl|wget|ssh|scp|git push|npm publish|docker)[^"]*\*\)"' "$f" \
    | sed 's/^/   BROAD: /'
done < <(find "$root" -path '*/node_modules' -prune -o -type f \
  \( -name 'settings.local.json' -o -name 'settings.json' \) -path '*/.claude/*' -print0 2>/dev/null)
[ "$found" = 0 ] && echo "No .claude/settings*.json under $root"
EOF
bash /tmp/claude-settings-audit.sh .

在一个含六条规则的合成文件上,它标出了嵌入数据库密码、bearer token 和 DATABASE_URL=... 赋值的那三条,外加 Bash(*)、Bash(rm -rf *),以及在 BROAD 项下被第二次标出的那条 curl 规则。把这个文件提交到一个测试仓库后,它还打印出了 TRACKED BY GIT 那一行。它本质上就是一段 grep,命中结果要你自己看。

命中之后怎么处理:

  1. 先轮换凭据。 只要这个文件曾经被推送过,就把密钥当作已公开。一旦有 fork 或爬虫拿到了,改写历史也无济于事。
  2. 删掉这条规则,或者换成一条不带密钥的:放行 psql *,密码通过 shell 环境变量 PGPASSWORD 传入,而不是写在命令里。
  3. 如果 git 在跟踪这个文件,执行 git rm --cached .claude/settings.local.json,并把 .claude/settings.local.json 加进仓库自己的 .gitignore,让保护跟着仓库走,而不是依赖每台机器的全局 excludes。
  4. 也检查一下 .claude/settings.json。 这个文件本来就是要提交的,里面出现带密钥的 allow 规则就不是意外泄露,而是设计上的泄露。

出于同样的原因,提交的配置文件里优先用 deny 和 ask 规则,个人的 allow 规则留在本地。deny 和 ask 规则立即生效;仓库里的 allow 规则要等每位同事信任该文件夹之后才生效。

第 5 步:检查 IDE 扩展和 SSH 密钥

有后门的版本是通过 Cursor Marketplace 分发的 Claude Code for VS Code 扩展 2.1.312 和 2.1.313,由 Phorion 于 10 月 30 日披露,Cursor 已将其下架。据 Phorion 描述,载荷在扩展模块加载时立即执行,往 ~/.ssh/authorized_keys 追加一把注释为 svc-telemetry 的 Ed25519 公钥,尝试开启远程登录,并把主机信息回传。在 macOS 上,由于植入程序自身的 bug,非管理员用户的 SSH 开启步骤会失败,但公钥和持久化照样会被写下。

列出已安装的版本:

cursor --list-extensions --show-versions | grep -i claude
code --list-extensions --show-versions | grep -i claude
ls ~/.cursor/extensions ~/.vscode/extensions 2>/dev/null | grep -i claude

在我的机器上返回的是 anthropic.claude-code@2.1.302,不在受影响范围内。接着检查 Phorion 公布的指标:

grep -n "svc-telemetry" ~/.ssh/authorized_keys 2>/dev/null || echo "no match"

Phorion 指出这个注释字符串由攻击者控制,所以 grep 没命中并不等于安全。如果你从 Cursor Marketplace 跑过 2.1.312 或 2.1.313,请删掉 authorized_keys 里所有你不认识的公钥,检查登录项和 launch agents,并把这台机器视为已被攻陷,直到做过应急响应排查为止。扩展请从 VS Code Marketplace 安装,或者在 claude CLI 里用 /ide 让它安装;从 2.1.318 起,Claude Code 不再在 IDE 终端内自行安装或更新扩展。

第 6 步:User-Agent 带邮箱问题的修复

Issue #107757 报告 Claude Code 在搜索和抓取网页时,把账号邮箱放进了请求的 User-Agent 头,网站和追踪器因此可以收集到它。2.1.317 的更新日志条目日期为 11 月 3 日,原文是:"Added a check that asks before a web or shell tool call sends your account email address to the network."。报告者是在 2.1.318 上提交的 issue,到 11 月 8 日它仍然处于打开状态,有近 1,000 个 reaction,最终结论请持续关注。

今天能做的:升级(claude update);如果你是无人值守运行,要知道在 denyInsteadOfPrompting 或 dontAsk 下,这个新增的「先询问」检查会变成直接拒绝,而这正是你想要的行为。

每道防线拦住了什么:实测 vs 文档

尝试blockWrites…Read deny 规则Bash(curl *) denydontAsk / -p 默认沙箱
Write 工具创建 ~/x.txt拒绝(实测)——提示 → 拒绝沙箱放行,文件工具不在沙箱内
Read 工具读取 .env—拒绝(实测)—放行,读取不需要审批—
在 Bash 里 cat .env—拒绝(实测)—拒绝—
在 Bash 里 echo x > ~/x.txt不覆盖——提示 → 拒绝拦截(文档:macOS 上报 Operation not permitted)
在 Bash 里 curl https://…——拒绝(实测)拒绝除非主机在白名单内,否则拦截
在 Bash 里 python3 -c …———拒绝(实测)在沙箱内运行
git log、ls 等只读命令———运行(实测)运行
rm -rf ~———拒绝,没有任何规则能放行拦截,home 目录不在可写集合内(文档)

「实测」指我于 2026 年 11 月 8 日在 2.1.319 上亲自跑过。其余内容均来自本文链接的官方文档。

可选:把 shell 也关进沙箱

上面的配置圈住的是 Claude 自己的文件工具。shell 命令要靠 Bash 沙箱 (opens in a new tab)来圈,它默认是关闭的。沙箱在 macOS 上用 Seatbelt,在 Linux 或 WSL2 上需要 bubblewrap 和 socat。只对一个项目开启,可以在会话里输入 /sandbox;对所有项目开启,则在 ~/.claude/settings.json 里加上:

{
  "sandbox": {
    "enabled": true
  }
}

要确认它在工作,让 Claude 执行 touch ~/sandbox-probe。在沙箱内它会失败,macOS 上报 Operation not permitted,Linux 上报 Read-only file system。如果 Claude 提议在沙箱外重试这条命令,拒绝它。

把这几项组合起来,就补上了上面矩阵里的缺口:文件工具由 blockWritesOutsideWorkingDirectories 圈住,shell 由沙箱圈住,密钥由 Read deny 规则圈住,无人值守运行由 denyInsteadOfPrompting 圈住。这时 bypass 模式只是把剩下的提示跳过去,不再额外开放什么。如果某个受控任务仍然需要 bypass,bypass permissions 指南讲了开关位置和对应的报错信息。

常见问题

相关指南