权限规则:deny、ask、allow
用 settings.json 把“绝对不行”和“必须确认”写进系统,而不是写进提示词
把“绝对不行”写进 deny,“必须我点头”写进 ask,只读和本地开发的命令写进 allow。这些规则由 Claude Code 执行,不靠模型自觉:CLAUDE.md 里写“不要 force push”只是建议,写进 deny 才是规则。
规则怎么生效
- 顺序固定:先 deny,再 ask,最后 allow。 第一个命中的说了算,和规则写得多具体无关:宽泛的 deny 会盖过具体的 allow,ask 也会盖过 allow
- 多个文件的规则会合并,不是覆盖。 任何一个文件里的 deny,别的文件里的 allow 都放不开
- 写法是前缀加通配。
Bash(git push --force *)里,第一个*之前的部分按原样匹配;末尾的“空格 +*”也能匹配不带参数的命令本身,所以Bash(wrangler deploy *)连光秃秃的wrangler deploy也会拦住 - 复合命令拆开匹配。
cd app && rm -rf build里任何一段命中 deny 或 ask,整条命令都会被拦下
随时可以用 /permissions 查看所有规则,它会标出每条规则来自哪个文件。细节见官方文档。
我实际用的规则
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~)",
"Bash(sudo rm -rf *)",
"Bash(git push --force origin main *)",
"Bash(git reset --hard origin/main *)"
],
"ask": [
"Bash(gh api * -X DELETE *)",
"Bash(gh repo delete *)",
"Bash(gh pr merge *)",
"Bash(make db-reset *)",
"Bash(docker volume rm *)",
"Bash(docker compose down -v *)",
"Bash(psql * -c DROP*)",
"Bash(wrangler deploy *)",
"Bash(vercel --prod *)",
"Bash(wrangler d1 execute * --remote *)"
],
"allow": [
"Bash(wrangler whoami)",
"Bash(vercel ls *)",
"Bash(gh pr view *)",
"Bash(gh run list *)"
]
}
}三组规则的逻辑:
- deny:不可逆,而且 agent 永远没有正当理由去做的事:删根目录和家目录、force push 到 main、把 main 硬重置成远端的样子
- ask:正当但影响大的事:通过 API 删除资源、删仓库、合并 PR、重置或删除数据库、删 Docker 卷、部署生产、往远端数据库写数据。这些我要亲自点头
- allow:只读和本地开发的命令。放行它们是为了少弹窗。弹窗少了,剩下那几个你才会认真看
想从历史记录里自动整理出常用只读命令的放行列表,可以用内置的 /fewer-permission-prompts。
这些规则拦不住什么
Bash 规则匹配的是命令文本,官方文档明确说它不是程序级的安全边界:Bash(rm *) 拦得住 rm -rf build/,拦不住 /bin/rm -rf build/ 和 bash -c 'rm -rf build/';Bash(git push *) 拦不住 git -C . push。
我自己的规则里就有写得不够好的。按官方文档的匹配规则推算:
Bash(gh api * -X DELETE *)有两个通配,要求-X DELETE前后都还有内容。最常见的gh api -X DELETE repos/o/r和gh api repos/o/r -X DELETE都匹配不上,--method DELETE也不行。改成Bash(gh api *DELETE*)更稳,误报的代价只是多问一次Bash(psql * -c DROP*)要求-c后面紧跟大写的DROP。真实命令几乎都带引号(-c "DROP TABLE ..."),写成小写也很常见,很可能匹配不上- ask 里只写了
Bash(wrangler deploy *),没写Bash(bunx wrangler deploy *)。规则按命令文本匹配,不会自动去掉bunx这类前缀,用 bunx 部署时这条拦不住,两种写法都要列(见小工具上线)
所以权限规则只能拦住 agent 通常的写法。需要正则、不区分大小写这类更强的匹配,交给 hook;需要真正的隔离,用官方的沙箱(/sandbox)。
放在哪个文件
| 文件 | 适合放什么 |
|---|---|
~/.claude/settings.json(用户级) | 对所有项目都成立的 deny:删家目录、force push 到 main |
.claude/settings.json(项目级,进 git) | 这个仓库特有的 ask:部署、数据库命令,团队共享 |
.claude/settings.local.json(项目级,不进 git) | 只对你本机成立的 allow |
| 托管设置 | 最关键的几条 deny,普通工具改不到 |
用户级文件有个弱点:配置切换工具会整份重写的,正是这个文件(见切换工具会整份覆盖设置)。
关键规则放托管设置
试用中 托管设置本来是企业管理员下发策略用的,优先级最高,用户级、项目级设置和命令行参数都覆盖不了它。一个人用也可以借这个机制:把最关键的几条 deny 放进去。写这个目录需要管理员权限,平时用的工具碰不到它。这是我在三层护栏全部失效之后的补法,还在试。
| 系统 | 路径 |
|---|---|
| macOS | /Library/Application Support/ClaudeCode/managed-settings.json |
| Linux、WSL | /etc/claude-code/managed-settings.json |
| Windows | C:\Program Files\ClaudeCode\managed-settings.json |
动手之前先确认这个文件存不存在。已经存在、而且不是你写的(公司电脑、管理员下发的策略),就交给管理员,不要自己改,改了也可能被下一次下发覆盖。下面以 macOS 为例。
文件不存在时才新建。这段命令自带检查,文件已经存在就什么都不改,只把内容打印出来:
f="/Library/Application Support/ClaudeCode/managed-settings.json"
if [ -e "$f" ]; then
echo "托管设置已经存在,没有改动。先看看内容:" && sudo cat "$f"
else
sudo mkdir -p "$(dirname "$f")"
sudo tee "$f" > /dev/null <<'EOF'
{
"permissions": {
"deny": [
"Bash(rm -rf /)",
"Bash(rm -rf ~)",
"Bash(git push --force origin main *)",
"Bash(git reset --hard origin/main *)"
]
}
}
EOF
fi文件已经存在、而且是你自己以前写的:先备份,再把新规则合并进去,不要整份覆盖:
f="/Library/Application Support/ClaudeCode/managed-settings.json"
sudo cp "$f" "$f.bak-$(date +%Y%m%d-%H%M%S)"
tmp="$(mktemp)"
sudo cat "$f" | jq --argjson add '["Bash(rm -rf /)","Bash(rm -rf ~)","Bash(git push --force origin main *)","Bash(git reset --hard origin/main *)"]' \
'.permissions.deny = (((.permissions.deny // []) + $add) | unique)' > "$tmp" \
&& sudo cp "$tmp" "$f"
rm -f "$tmp"
sudo cat "$f" | jq empty && echo "JSON 有效"最后一行别省:托管设置不是合法的 JSON 时,Claude Code 会直接拒绝启动。然后新开一个会话,运行 /status:Setting sources 一行出现 Enterprise managed settings (file),就说明生效了(见官方文档)。
两点提醒:托管设置对这台机器上的所有项目都生效;本机管理员仍然能改它,所以它防的是工具,不是你自己。另外,不要在个人电脑上设 allowManagedPermissionRulesOnly,它会让你在其他文件里写的权限规则全部失效。
bypass 模式下,ask 规则靠得住吗
试用中 这一条请你自己验证,不要直接相信我或任何一篇文章。
官方文档(截至 2026-10)的说法是:deny 规则在所有模式下都生效,包括 bypassPermissions;命中 ask 规则的操作,在任何模式下都不会被自动放行,也包括 bypassPermissions(原文)。但 bypass 模式的实际行为在过去一年里随版本变过不止一次,官方仓库里有一长串相关 issue。如果你和我一样习惯开着 bypass 图省事,就别把关键防线押在 ask 上。
用一条无害的规则,测一下你当前的版本和配置:
在 .claude/settings.local.json 里加一条测试规则(已经有 permissions 的话合并进去):
{
"permissions": {
"ask": ["Bash(echo ask-check *)"]
}
}用你平时的模式启动 Claude Code,让它运行 echo ask-check hello。
弹出确认,说明 ask 在你的环境里有效;直接执行了,说明不能依赖它。测完删掉这条规则。
不管结果如何,分工都一样:不可逆的放 deny,需要兜底的放 hook。官方文档写明,PreToolUse hook 返回 deny 时,即使在 bypass 模式下也会拦下。想少弹窗又不想完全放开,官方更推荐 auto 模式:由一个分类器在后台审查每个动作。
硬规则下沉
判断标准:能机械验证的进系统,需要判断的留在提示词里。
| 规则 | 放哪 |
|---|---|
| 某条命令绝对不能执行 | deny |
| 某类操作必须我确认 | ask |
| 执行之前必须先做某件事,比如备份 | hook |
| 某些文件不能读、不能改 | deny 里加 Read(./.env)、Edit(./secrets/**) 这类规则 |
| 需要判断的事:什么算 trivial 修复、什么时候必须先写 spec | CLAUDE.md |