docs(hook-pitfalls): #33 基于 shlex 的守卫看见的是你打的字,不是 bash 构造的 argv - #299
Merged
Conversation
2026-08-16:fresh-context 独立审阅在我已宣布守卫可靠**之后**,找出三条零设置、
日常语法的完全绕过——都能让守卫的目标形态 `git commit -a` 对它隐形,
其中一条被推到真 git 执行、真把未提交改动卷进了工作树。
git commit {-a,-m} x → 实 argv = commit -a -m x(花括号展开)
git commit $'-a' -m x → 实 argv = commit -a -m x(ANSI-C 引用)
git${IFS:0:1}commit${IFS:0:1}-a → 实 argv = git commit -a(IFS 当分隔符)
根因是一个范畴错误:shlex 是**词法器**,切词 + 处理引号;而 bash 在拿到 argv 之前
还要做花括号/波浪号/参数/命令替换/算术展开、词分割、路径名展开。shlex 一个都不做、
也从没声称要做。守卫按自己的口径没 bug,它只是在回答另一个问题——
「用户打了什么」而不是「实际会跑什么」。
条目里给了 5 条修法,以及两条更该记住的元教训:
① **自己的绕过排查会被"刚修的那个 bug"塑形,因而恰好在下一个 bug 所在处最盲。**
我自己那轮对抗测试 26 条危险形态,全部围绕注释/重定向/续行/引号——
没有一条用到 shell 展开,因为展开不在我当时脑子里的那个 bug 里。
拿到「找任何绕过」这句话的 fresh-context 审阅者没有这个锚,就找到了。
② **「我们接受一部分漏报」是预算,会悄悄超支。** 该守卫早已声明放弃变量间接
(`$G commit -am`),但那要求先写一条赋值语句;这三条零设置、且是人们
出于非规避理由天天在打的语法(`cp file.{txt,bak}` 完全日常)。
声明的边界很窄,实际的洞很宽。
修法要点:不必实现 bash 展开(那是 shfmt 级的活),只要让危险旗标别再被藏住——
分词前在原始文本、且只在引号外把 `{a,b}` 归一成 ` a b `、`$'x'` 成 `x`、
`${IFS…}` 成空格即可;花括号规则须限定"含逗号且不含空白",否则会吃掉
`find -exec {} \;` 与 `awk '{print $1}'`。
以及必须量:真实语料 27,641 条相关命令跑前后两版,**退出码差异 0 条**——
三条完全绕过被堵死,对真实行为零影响。给 Tier-0 闸门加归一化器却不给这个数字,
就是无界的误杀风险,而闸门上的误杀比你刚补的那个洞更糟。
守卫本体的修复见私有仓 daymade/scripts f0ff4a3(套件 77→97;双向标定对修复前版本
86-11,红的恰好是 8 条绕过 + 3 条误杀,正控在两版都绿)。
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
接 #297 / #298。fresh-context 独立审阅在我已宣布守卫可靠之后,找出三条
零设置、日常语法的完全绕过——都让守卫的目标形态
git commit -a对它隐形。三条绕过(均已用 probe 脚本核过真实 argv)
git commit {-a,-m} xcommit-a-mx(花括号展开)git commit $'-a' -m xcommit-a-mx(ANSI-C 引用)git${IFS:0:1}commit${IFS:0:1}-agitcommit-a(IFS 当分隔符)其中
{-a,-m}被审阅者推到真 git 执行,真把未提交改动卷进了工作树。三条都可回溯到守卫最早的提交,不是新引入的。
根因是范畴错误
shlex是词法器:切词 + 处理引号。而 bash 在拿到 argv 之前还要做花括号/波浪号/参数/命令替换/算术展开、词分割、路径名展开——
shlex一个都不做,也从没声称要做。所以
{-a,-m}作为一个以{开头的字面 token 到达,永远进不了只对
-开头 token 生效的旗标扫描器。守卫按自己的口径没 bug,它只是在回答另一个问题:
「用户打了什么」而不是「实际会跑什么」。
两条元教训(条目正文里的重点)
我那轮对抗测试 26 条危险形态,全部围绕注释/重定向/续行/引号——
零条用到 shell 展开,因为展开不在我当时脑子里的那个 bug 里。
(
$G commit -am),但那要求先写一条赋值语句;这三条零设置,且是人们出于非规避理由天天在打的语法(
cp file.{txt,bak}完全日常)。附带修掉的一条误杀(Finding C)
N>&word只有 word 整个是数字时才是 fd dup,否则是到文件的重定向(实测
2>&1x真在磁盘建出名为1x的文件)。原正则只写了纯数字那支,于是
git commit -- 2>&1x被误杀。数字
无一旁落;正控(
cp file.{txt,bak}/awk '{print $1}'/find -exec {} \;/message 里写花括号是数据)在两版都绿——证明它们不是靠修复才通过的
给 Tier-0 闸门加归一化器却不给最后这个数字,就是无界的误杀风险——
而闸门上的误杀比刚补的那个洞更糟。
守卫本体修复在私有仓(
daymade/scriptsf0ff4a3)。🤖 Generated with Claude Code