Claude Code 权限加固:禁止写入仓库之外、权限提示直接拒绝、审计 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 *)"
]
}
}每一行都在换什么:
| 配置项 | 作用 | 引入版本 |
|---|---|---|
blockWritesOutsideWorkingDirectories | Write、Edit、NotebookEdit 三个工具拒绝工作目录之外的任何路径,在所有权限模式下生效,包括 bypassPermissions | 2.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 步的审计脚本 |
这三件事没有一件能靠切换权限模式解决。需要的是一份配置文件、一次审计和一次版本检查。
- Claude Code 权限加固:禁止写入仓库之外、权限提示直接拒绝、审计 settings.local.json
- Python 打包成 exe 的 4 种方法:Runcell、PyInstaller、Nuitka 与 cx_Freeze
- pandas 3.1 升级指南:inplace 弃用警告、df.select() 与 read_csv 并行加速
- OpenCode 2.0 升级教程:从 1.x 迁移配置、插件与会话(附回滚方法)
- Python 3.15 安装与升级指南:用 uv 安装、延迟导入提速、数据科学库兼容性实测
- Claude Code Mods 教程:从零写出第一个 Mod、测试与安装,附 15 个社区案例
- Pi Coding Agent 1.0 使用教程:安装、接入模型(含 Ollama 本地模型)并跑通第一个任务
- Claude Code 现在会读 AGENTS.md:配置方法、四种加载模式,以及如何与 Codex、OpenCode、Cursor 共用一份文件
- GPT Image 2.5 使用指南:Flare 与 Sunburst 怎么选、API 调用与价格
- DeepSeek Harness 安装教程:一条命令装好 DSH 并跑通第一个任务
- Runcell Science:面向科研的开源 Claude Science 替代方案
- Mac 怎么不休眠:合盖继续运行 Codex、Claude Code 和本地 AI Agent
- OpenClaw vs ZeroClaw vs Pi Agent vs Nanobot:2026 年该选哪个 AI Agent 技术栈?
- Claude Code 能分析 Jupyter Notebook 吗?Data Science 场景下它到底做了什么
- Claude Code Routines 是什么?AI Agent 定时任务与自动触发指南
- Claude Code Desktop 绕过权限:如何开启 Bypass permissions
- 如何用 Google 的 A2A 协议构建两个 Python Agent:一步步教程
- 2025 年 Python 增长最快的 10 个数据可视化库
第 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 运行遇到权限提示时直接拒绝而不是干等。按运行发生的场景来选。
| 场景 | 用什么 | 行为 |
|---|---|---|
| 你走开了的交互式终端会话,包括它的子代理和 teammates | settings 里设 "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,命中结果要你自己看。
命中之后怎么处理:
- 先轮换凭据。 只要这个文件曾经被推送过,就把密钥当作已公开。一旦有 fork 或爬虫拿到了,改写历史也无济于事。
- 删掉这条规则,或者换成一条不带密钥的:放行
psql *,密码通过 shell 环境变量PGPASSWORD传入,而不是写在命令里。 - 如果 git 在跟踪这个文件,执行
git rm --cached .claude/settings.local.json,并把.claude/settings.local.json加进仓库自己的.gitignore,让保护跟着仓库走,而不是依赖每台机器的全局 excludes。 - 也检查一下
.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 *) deny | dontAsk / -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 指南讲了开关位置和对应的报错信息。
常见问题
相关指南
- Claude Code Desktop 绕过权限:如何开启 Bypass permissions
- Claude Code Mods 教程:从零写出第一个 Mod、测试与安装,附 15 个社区案例
- Claude Code AGENTS.md 配置指南:四种加载模式、与 CLAUDE.md 的区别、多工具共用
- Claude Code Routines 是什么?AI Agent 定时任务与自动触发指南
- 并行代码智能体指南:2026 年的 Worktree、沙箱与桌面工具模式