[控场AI]
· 8 分钟阅读· 4,441 字

22种方式绕过Claude Code推送规则,仅7种被硬拦截

22种方式绕过Claude Code推送规则,仅7种被硬拦截

Claude Code deny规则仅拦截7/22种强制推送,PreToolUse hook补全阻断。

文章通过22种真实git命令测试Claude Code的deny规则,发现仅7种被硬拦截,15种通过refspec变形、git全局选项、包装器和脚本文件绕过。作者指出官方文档也承认Bash规则不是安全边界。为此提出PreToolUse hook方案,通过拆分、解包、检查三步解析完整命令,可阻断全部22种命令且不误伤特性分支推送,并附带52个Vitest用例防止误报。文章还记录了无头模式、未出生分支等实践坑,并明确hook无法覆盖自有终端、CI、间接脚本和API推送,最终兜底仍是GitHub main分支保护。

团队在 .claude/settings.json 中写入 "deny": ["Bash(git push --force *)", "Bash(git push -f *)", "Bash(git push * main)"],看似封死了强制推送和直推 main 的路径。但一位开发者用 22 种真实命令逐一测试后发现,只有 7 种被硬拦截,其余 15 种均绕过了 deny 规则(多数仅触发权限提示)。加上一个 PreToolUse hook 后,22 种全部被阻断,且正常的特性分支推送不受影响。

测试结果概览

测试方法:隔离环境中的逐条验证

测试在一个一次性克隆中进行,origin 指向临时目录里的本地裸仓库,不涉及真实 GitHub 远程。规则层使用无头会话(claude -p --model haiku --setting-sources project)逐条执行命令,读取工具结果判断是“已被拒绝”还是“需要审批”。Hook 层则直接将命令 JSON 通过 stdin 传给 PreToolUse 脚本,检查退出码。

作者强调,提示不等于拦截:“deny 规则是一堵墙,提示只是点击一次就能运行,而在 bypassPermissions 模式或宽泛的 allow 规则下,连点击都不需要。”对于团队决定永不允許的操作,任何非硬阻断都是缺口。

Claude Code 的权限系统与 settings.json

Claude Code 的权限体系由项目级 .claude/settings.json 中的三类规则构成:allow、ask、deny。allow 中的规则让匹配的调用无需确认直接执行;ask 中的规则让调用弹出审批提示;deny 中的规则则直接拒绝调用。规则以 Tool(matcher) 形式书写,例如 Bash(git push *) 会按前缀和通配符匹配 Bash 命令字符串。此外还有 bypassPermissions 模式,在该模式下所有非 deny 的调用都不再询问,因此对于“永不允許”的操作,只有 deny 才是可靠拦截。PreToolUse hook 是另一层机制,在权限规则评估之前执行,可读取完整工具输入并通过退出码(如 2)强制阻断,从而弥补字符串匹配规则的不足。

15 种绕过方式暴露四类漏洞

未被拦截的命令可归为四组:

1. Refspec 变形。git push origin +main、HEAD:main、main:main 等不以 main 结尾,导致 git push * main 规则无法匹配。

2. git 自身选项。git -C . push、git -c core.askPass=true push 以及别名(如 git -c alias.p=push p -f origin main)在 git 和 push 之间插入参数,绕过了模式匹配。

3. 其他执行方式。/usr/bin/git push、sh -c "git push -f"、bash -lc 'git push --force'、eval、xargs -I{} 等包装器让规则看不到真实的 git 调用。

4. 脚本文件。bash ship.sh 只暴露脚本名,规则无法看到内部包含的强制推送。

有价值的是,Claude Code 的匹配器并非全无作为:它能正确拆分 && 和 ; 并检查每一部分,也能剥离环境变量前缀,因此 GIT_TRACE=0 git push -f、cd . && git push --force 和 git status; git push -f 被成功拒绝。官方权限文档也坦承,Bash 规则“不是程序的安全边界”,并明确列出 git -C . push 和 sh -c 是 git push * 规则无法匹配的形式。

另外,15 种绕过中有 6 种之所以仍出现提示,是因为团队额外配置了宽泛的 ask: git push * 规则。若没有这条规则,第 4 至第 9 种命令距离自动批准仅一步之遥。

Refspec 与 git 全局选项

Refspec 是 git push 中指定源和目标引用的表达式,常见形式有 src:dst、+refs/heads/main:refs/heads/main 等,其中 + 前缀表示强制更新。由于规则 git push * main 依赖命令字符串以 main 结尾或含特定片段,HEAD:main、main:main、origin +main 等写法改变了末尾形态,导致模式无法命中。另一方面,git 允许在子命令前插入全局选项,如 git -C <path> 指定工作目录、git -c key=value 临时覆盖配置(包括定义别名 alias.p=push)。这些选项位于 git 与 push 之间,使得按字面匹配 git push 的规则失效。理解这两类语法是设计可靠守卫的前提:必须解析出真实的子命令、标志和 refspec,而不能只做字符串前缀匹配。

修复方案:读取完整命令的 PreToolUse Hook

Hook 在权限规则之前运行,退出码为 2 时调用被阻断,即使 allow 规则本应放行。因此 hook 可以检查完整命令文本并做出判断。其工作分三步:

  1. 拆分:按 &&、||、;、|、换行、$(...) 和反引号将命令拆为简单命令,保留引号内字符串。
  2. 解包:丢弃 FOO=bar 前缀和 env、timeout、xargs、nohup、exec 等包装器,将 /usr/bin/git 还原为 git,并递归进入 sh -c/bash -c 字符串、eval 参数和本地脚本文件。
  3. 检查:对任何 git push,跳过 git 全局选项(-C、-c,并识别 -c alias.x=push),再检查标志和 refspec。

核心判断逻辑(摘自真实文件)会检测 --force、-f、--mirror、--all、--delete 等标志,以及对 + 开头的 refspec 和受保护分支目标。对于无法解析为 git 调用的文本(如 python3 -c "subprocess.run(['git','push','-f',...])"),则回退到正则:git + push + 强制标志或受保护分支名即阻断。

hook 通过 settings.json 中的 PreToolUse 配置接入,与 deny 规则并存。作者建议保留 deny 规则作为第一层防线,并在测试后补充了 Bash(git push * +*)、Bash(git push * *:main)、Bash(git push * *:refs/heads/main) 以及 --mirror 形式和 master/production 分支的规则。

该 hook 附带 52 个 Vitest 用例(39 个应阻断、12 个应放行,外加当前分支检查),随团队常规 npm test 运行。测试很关键:此类守卫最容易因误报而失效,如果它阻断了 git push origin feature/main-menu,人们会直接关掉它。

PreToolUse hook 的退出码语义

在 Claude Code 中,PreToolUse hook 通过 settings.json 的 hooks 字段注册,可针对 Bash 等工具在调用前运行。hook 从 stdin 接收包含工具名和输入的 JSON,其退出码决定后续行为:退出码 0 表示放行并继续走正常权限流程;退出码 2 表示硬阻断,工具调用不会执行,且该阻断优先于 allow 规则;其他非零退出码通常视为错误。正因如此,hook 能在字符串匹配规则之外读取完整命令文本,甚至递归解析包装器和脚本内容,从而检测出 deny 模式漏掉的强制推送。但 hook 本身也是本地配置,可被有权限者修改,因此它提高的是默认安全路径的可靠性,而非不可绕过的安全边界。

实践中踩到的坑

  • claude -p 在未信任文件夹中忽略 allow 规则,并在 stderr 输出警告。deny 和 ask 规则仍然生效,测试有效,但不应将无头运行直接等同于团队实际配置行为。
  • -p 模式下提示会静默变为拒绝,工具结果显示“需要审批”且不执行。因此测试中“提示”列实际未到达远程,但在交互式或自动批准会话中,那 15 种命令只差一步。
  • git rev-parse --abbrev-ref HEAD 在无提交仓库中失败,导致初版 hook 误拦了特性分支的普通 git push。改用 git symbolic-ref --short HEAD 可处理未出生分支;确实无法确定分支时(detached HEAD),hook 选择阻断。
  • 在 bypass 模式下运行测试未成功,Claude Code 的安全分类器拒绝为无人值守会话启动 bypassPermissions 模式。不过 hook 不需要实时会话,通过管道传入 JSON 是文档推荐的测试方式。

它保护不了什么

作者坦率列出边界:Claude Bash 工具之外的一切(自有终端、git GUI、CI);无法读取的间接调用(npm run release、Makefile 目标、编译二进制、运行时构建命令的脚本);不使用 git CLI 的推送(gh api 或 curl 调用 GitHub API);以及任何编辑 hook 的人。最终的兜底仍然是 main 分支保护:要求审查、禁止强制推送。hook 让安全路径成为 Claude Code 内的默认,而服务器让安全路径成为唯一。

完整设置(hook、测试、deny 规则、团队 playbook 和原始结果)已在免费演示仓库中公开。

#Claude Code#git push 规则绕过#PreToolUse hook#deny 规则#分支保护#AI 编程安全
分享:

相关推荐