16 KiB
最终独立审查:fix/guard-test-blindspot-coverage(终审)
- 审查对象:
7a5c80eacb835bfc6e62b3557129ec299bbb5290..HEAD(分支fix/guard-test-blindspot-coverage,HEAD=6ddbae089f61dcda8d74d7f5980fb81631e30ac5,经git rev-parse确认即分支尖端) - 涉及文件:
tests/test_shell_pipe_hazard.py(+406/−44)、scripts/e2e-duties.sh(2 处)、scripts/hooks/pre-commit(1 处)、scripts/svc.sh(1 处)、任务卡与首轮评审文档(.task/**,host 豁免路径) - 审查身份:全新独立会话,未参与实现;只读审查,除本报告外未修改任何文件;合成脚本与探针全部位于 /tmp,仓库内无残留
- 审查日期:2026-10-21
结论:BLOCKED
历次评审的全部阻塞项(首轮 B1 子壳/花括组包裹、B2 awk nextfile、B3 while read break、绝对路径与 exec 前缀;第二轮 mapfile/readarray -n 与 read -n)在当前 HEAD 已全部闭合并有运行时 141 实证;四处脚本改写语义等价、发现机制稳定、契约表述诚实。但按本轮判据第 (2) 条与可证伪约束,实测构造出 2 个此前未覆盖、运行时确认 pipefail 下 141、检测器不报 的 while/until 已声明命令族内其它停止写法:
seq 1 200000 | until read -r l; do :; done(条件 read 首行成功即终止循环,无 break);seq 1 200000 | while read -r l; do exit; done及条件形[ "$l" = 7 ] && exit(循环体以 exit 终止读取级子壳,无 break)。
按本轮约束「构造至少一个此前未覆盖的早退读取器形态……如报不出,作为阻塞项」,判 BLOCKED。不推翻已闭合项与等价性结论。
历次评审闭合核验(全部闭合,含独立复测)
| 历次阻塞项 | 闭合证据(本轮独立复测) |
|---|---|
R1-B1 子壳/花括组包裹 | (head -1)、| { head -1; } |
检测器报 (1,'(head -1)')/(1,'{ head -1; }');pipefail 实测 141。且帧设计自然覆盖组内嵌套管道:seq | (cat | head -1)、seq | { cat | head -1; }、seq | ( ( head -1 ) ) 均报,实测均 141 |
| R1-B2 awk nextfile | awk '$1 == 7 { print; nextfile }' 报;141 实测(套件 test_wrapped_and_prefixed… 内含 bash 运行时断言) |
| R1-B3 while read + break(含 break N、brace 内 break) | while read -r l; do break; done、do break 2; done(嵌套 for)、do { break; }; done 均报,实测 141;do (break); done 不报且实测 rc=0——子壳 break 本就不出外层循环,判定正确(无误报) |
| R1 绝对路径 / exec 前缀 | | /usr/bin/grep -q 1、| exec grep -q 1、| exec /usr/bin/head -1、| /usr/bin/env LC_ALL=C grep -q 1 均报 |
| R2 mapfile/readarray -n | -n1、-t -n 3、-tn2、-u 0 -n 1、--n 1、--numlines 1、builtin mapfile -n 1、{ mapfile -n 1 arr; } 均报;套件对 -n 1/read -n 5 附带 bash 运行时 141 断言并通过 |
| R2 read -n | read -n 5 -r line、read --chars 5 line 均报(read 族「任意用法」) |
重点核验(按本轮 5 项)
1. READER_FAMILIES 七族自检与族外不误报 — 通过
READER_FAMILIES(tests/test_shell_pipe_hazard.py:23)7 族非空三元组;test_declared_reader_families_each_flagged(:548)以cases字典逐族断言「声明族必有被命中合成用例」,缺族/空字段即失败——契约↔测试 linkage 成立,套件 11/11 通过。- 族外安全写法不误报:套件 33 行安全清单通过;本轮追加探针
seq \| (cat)、seq \| env FOO=1 command cat、seq \| LC_ALL=C grep -c 1、seq \| mapfile -t arr、out-of-family 读者dd/jq -n/perl -ne/xargs -n1全部不报。两处既有安全方向误报(awk 引号串 "exit"、算术按位或)见 N1/N3,均不涉当前受管脚本。
2. 族内同族写法漏检 — 发现 2 个阻塞项(见下),其余族闭合
- grep:
_grep_exits_early(:336)不因操作数停扫(非选项词仅跳过),故grep 1 -q、grep 1 --quiet、grep 1 -m1、grep -e 1 -q、grep -f file -q 1、grep -d skip -q 1全部报;grep 无 -q/-m 之外的提前退出选项。族内无漏检。 - sed:
[qQ][0-9]*覆盖q/Q/qN(sed 'q2'实测即带退出码 2 的立即退出,被报);GNU sed 无其它停止命令。词法不可见的-f progfile见 N7。 - awk:END 块剥离后查
exit|nextfile,BEGIN 内 exit 报、仅 END 内不报(读满输入,安全)——与声明一致。 - head/read:任意用法全报。
- mapfile:
-n簇内/独立/长选项全覆盖;词序倒置mapfile arr -n 1不报但 bash 内建 getopt 停于首操作数、-n不生效,实测 rc=0 读满输入——惰性写法,非危害(N4)。 - while/until:除 break 外仍有两种停止写法漏检 → B1/B2 阻塞。
3. 发现机制与受管脚本扫描 — 通过
managed_scripts()(:51)经git ls-files -z -- ':(glob)scripts/**/*.sh' ':(glob)scripts/hooks/*' + 2 个 EXTRA,本轮实测发现 17 个文件(含 3 个无扩展名 hooks);scripts/hooks/pre-commit无扩展名仍被:(glob)scripts/hooks/*命中。- 稳定性:
_lex+hazards显式跑遍 17 个脚本,0 解析错误、0 命中(与test_managed_scripts_have_no_early_exit_pipe_reader一致);EXEMPT={}且有过期条目守卫(:613)。 - ignored 陈旧克隆不入扫描:
git ls-files只列跟踪文件 +.gitignore:9runtime/(runtime-backups/:10同理);测试断言无runtime/前缀路径。本 worktree 无 runtime/ 目录,机制上也不可能漏入。 - 非阻塞残留(N2,首轮已提、本轮复核仍在):
scripts/agent-exec、scripts/dm、scripts/task(无扩展名 bash)与docker/entrypoint.sh不在 glob 范围;旧硬编码列表同样未含,无回归。
4. 四处脚本改写语义等价 — 通过(含写路径守卫)
探针(/tmp,同输入同状态对比,全部 SAME):
| 改写 | 状态覆盖 | 结果 |
|---|---|---|
ps … | grep -Fq → grep -Fq < <(ps …)(e2e-duties.sh:87 cleanup) |
活进程匹配(exec -a 伪造 argv)→ 双方 matched-kill;活进程不匹配 → no/no;死 PID → no/no | 三态等价 |
curl … | head -c 160 → head -c 160 < <(curl …)(e2e-duties.sh:128 log) |
1000B 取前 160B(165 字符含 |rc= 完全一致)、短体、生产者失联 → 三态输出与 rc 等价(此处替换状态本就不被消费) |
等价 |
echo "$branch" | grep -qE → grep -qE <<<"$branch"(pre-commit:9) |
13 组分支名(4 合法 + feat/x、feature/、feature//x、含空格、空串、大写、fix/-n pwned、换行名)退出码逐一相同 |
写路径守卫未削弱:合法分支照常 admit,非法照旧拒绝且文案不变;git rev-parse --abbrev-ref 的 detached="HEAD" 两侧同拒;here-string 尾随换行与 echo 一致 |
postgres --version | awk …exit → awk … < <(postgres --version) || true(svc.sh:4360 pgver) |
版本正常(24.1/24.1)、二进制失败(空/空)、空输出(空/空)、二进制缺失(空/空) | 捕获值与替换状态恒 0 两侧一致(|| true 保留) |
三脚本 bash -n 通过、均 #!/usr/bin/env bash(<()/<<< 合法);命令替换状态来源变化(head/awk 而非管道)在两处分别「不被消费」与「被 \|\| true 归零」,无行为差异。
5. 契约表述与实现一致 — 通过(无夸大承诺)
- docstring 明示「守护的承诺是『已声明的各族在发现到的脚本中处处被拦截』,而非『理解任意 bash 命令』」——承诺范围诚实收窄。
READER_FAMILIES7 条描述与实现逐条吻合:grep -q/-m(含 egrep/fgrep、长选项与=)↔_grep_exits_early;head 任意 ↔ 短路 True;sed q/Q ↔SED_QUIT;awk END 外 exit/nextfile(含 gawk/mawk/nawk 别名)↔_awk_exits_early;read 任意 ↔ True;mapfile-n/--n/--numlines(含 readarray)↔_mapfile_exits_early;while「循环体 break 出本循环(含 until)」↔_lexloops +break_out(brace 透明、子壳)正确截断、break N 层级计数)。- 测试名与内容相符;「…flagged_and_harmful」用例真实执行 bash 并断言 141,非纸面断言。
- 保留意见:while/until 族触发词限定为「break」是如实描述实现,但该族的其它停止写法(条件终止、体中 exit)落在声明触发词之外而命令族之内——正是本轮第 (2) 条要求排查的形态(见阻塞项)。
阻塞项
B1 检测器漏检:until 循环条件 read 首行成功即终止(无 break)
- 形态:
seq 1 200000 | until read -r l; do :; done。until条件为 read:首行读取成功 → 循环即结束 → 读取级子壳退出、stdin 关闭。语义等价head -1的纯 bash 写法,属已声明 while/until 命令族的另一种停止写法(触发机制是条件成功而非 break)。 - 运行时:
bash -c 'set -o pipefail; seq 1 200000 | until read -r l; do :; done'→ 141(/tmp 实测)。 - 检测器实测:
hazards(...)→[]。early_exit_reader(:392)对 while/until 仅查_lex(s)[1]的broken(break 置位),break_out(:113)只认break命令。 - 影响:
until read/until IFS= read -t …形态可使缺陷类静默回流;当前 17 个受管脚本无此写法(git grep "until "仅命中 ImageMagick-until参数),属未来回归面。
B2 检测器漏检:while/until 循环体内 exit 终止读取(无 break)
- 形态:
seq 1 200000 | while read -r l; do exit; done与惯用条件形… do [ "$l" = 7 ] && exit; done。「读到目标行即退出」是常见习语;管道末级 while 在子壳中运行,exit 直接终止子壳、关闭 stdin。 - 运行时:两形态
set -o pipefail下均 141(/tmp 实测)。 - 检测器实测:两形态均
[]——同 B1,break_out不识别exit。 - 影响:同族「读满即停」的第二个入口,词法可静态识别(循环体内命令位的
exit),未覆盖即构成本轮可证伪条款下的阻塞。 - 修复方向(供实施者参考,本轮未动代码):
break_out对循环帧内命令位的exit/exec <cmd>置broken;until循环在条件含声明族读者(read/head/…)时视为早退(while条件读者则读满,安全,现判定已正确)。是否扩展触发词到return(函数内管道级不可见,无意义)由实施者裁量。
两阻塞项均为「同一命令族(while/until)、运行时确认真实致害(141)、检测器不报」,满足本轮约束「如报不出,作为阻塞项」;亦满足首轮确立的「漏检使缺陷类可回流 = 阻塞」判据。合并修复面小(break_out + until 条件判定各一处),不影响其余已闭合结论。
非阻塞建议
- N1(沿首轮):
seq 1 200000 | awk '{print "exit"}'被误报——AWK_EXIT对程序文本裸\bexit\b,引号串未剥离。安全方向;当前受管脚本无触发。建议_awk_exits_early剥引号串后再查。 - N2(沿首轮):发现缺口——
scripts/agent-exec、scripts/dm、scripts/task(无扩展名 bash)与docker/entrypoint.sh不被:(glob)scripts/**/*.sh/:(glob)scripts/hooks/*/EXTRA 命中。建议增补:(glob)scripts/*与:(glob)docker/*.sh。 - N3(沿首轮):算术
$((0|read))的\|被当管道拆级,操作数恰为read/head时误报。极牵强、安全方向,可忽略。 - N4:
mapfile arr -n 1(选项在操作数后)不报——bash 内建 getopt 停于首操作数,-n不生效,实测 rc=0 读满输入,惰性无害;可加注释说明词序豁免理由。 - N5:包裹前缀白名单
PREFIXES(:45)未含nohup/timeout/nice/stdbuf等 exec 型包装命令;seq 1 200000 | timeout 5 head -1、| nohup head -1实测 141 且不报。属已声明「前缀」机制的可扩展清单之外(声明为「env/exec 等前缀」枚举),按可扩展契约记观察,不阻塞。 - N6(按可证伪边界记录):族外读者
dd(| dd bs=1 count=1实测 141)、jq -n、perl -ne 'exit if …'、xargs -n1、select循环(| select x in a; do break; done实测 141)均不报——非 READER_FAMILIES 声明族,依本轮明确边界只记非阻塞观察。 - N7:
sed -f prog.sed/awk -f prog.awk的程序文本在外部文件,词法不可见;与函数间接调用同级,契约「理解任意 bash 命令」之外,记观察。
判定标准对照
- 检测器漏检使该缺陷类可回流:成立(B1/B2,运行时确认 141)→ 阻塞。
- 七族自检 + 族外不误报:成立(第 1 节)。
- 发现机制稳定、无解析错误、ignored 陈旧克隆不入扫描:成立(第 3 节)。
- 改写引入语义回归:未发现(4/4 等价,含写路径守卫 13 组分支名探针)。
- 写守卫被削弱:未发现。
- 契约夸大承诺:未发现(承诺已按「已声明族 + 已发现脚本」收窄);while/until 触发词如实标注 break,B1/B2 按同族停止写法计阻塞而非虚言。
- 族外新命令族/构造:仅记 N5/N6/N7 非阻塞观察(边界条款)。
已运行的验证命令与结果(均只读;合成物全部位于 /tmp)
| # | 命令 | 结果 |
|---|---|---|
| 1 | git rev-parse HEAD fix/guard-test-blindspot-coverage |
二者同为 6ddbae089f61…;范围 7a5c80e..HEAD 即分支全量(diff --stat 6 文件) |
| 2 | PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_shell_pipe_hazard -v |
Ran 11 tests, OK |
| 3 | git ls-files -z -- ':(glob)scripts/**/*.sh' ':(glob)scripts/hooks/*' docker/verify-env.sh tests/load/collaboration/run_pgbench.sh |
17 文件(含 3 个 hooks 无扩展名脚本,无 runtime/) |
| 4 | head -1 + bash -n:scripts/e2e-duties.sh、scripts/hooks/pre-commit、scripts/svc.sh |
均 #!/usr/bin/env bash,bash -n 全过 |
| 5 | /tmp 探针:检测器注入形态矩阵(grep 词序/参数选项、until-read、while-exit、组嵌套管道、mapfile 词序、sed qN、前缀包装、族外读者、$((算术|))、awk 引号串) | 报:grep 全词序形态、组嵌套管道 (cat|head -1)/{ cat|head -1; }/( (head -1) )、while :; do read && break; done、sed 'q…';不报:until-read-nobreak、while+exit(B1/B2)、(break)(正确,实测 0)、mapfile 词序(实测 0)、族外读者、nohup/timeout 前缀;误报:awk 'exit' 串、$((0|read)) |
| 6 | /tmp pipefail 运行时基线(bash -c 'set -o pipefail; …') |
until-read、while+exit(无条件/条件)、(cat|head -1)、{ cat|head -1; }、( (head -1) )、{ break; }、read && break、grep 1 -q、dd count=1、select …break、timeout 5 head -1、nohup head -1 → 141;while :/(break)/read || break/mapfile arr -n 1/mapfile arr → 0 |
| 7 | /tmp 等价性探针(pre-commit 13 分支名、cleanup 3 态含真实匹配 PID kill、head -c160 3 态、pgver 4 态) | 全部 SAME |
| 8 | 模块化扫描:_lex+hazards 遍历 17 个受管脚本 |
errors=0,found=[] |
| 9 | git grep "until "、| while 于受管脚本 |
无 until 管道读者;7 处 | while 循环体均读满输入(printf/continue),无 exit/break 于读取级 |
| 10 | grep -n runtime .gitignore、git check-ignore 语义复核 |
.gitignore:9 runtime/(:10 runtime-backups/),ignored 克隆不可能入 git ls-files |
| 11 | sed 'q2'/sed '5q2'/sed '5q 2' 行为定性 |
q2 即退出码 2 立即退出(检测器 [qQ][0-9]* 命中);空格形惰性(rc=0),不构成漏检危害 |
可证伪性注入记录(约束第 4 条)
本轮人为注入 5 组此前套件未覆盖的族内新写法:grep 1 -q(词序倒置)→ 报 ✓;grep -e 1 -q(参数选项后置)→ 报 ✓;(cat | head -1)(组内嵌套管道)→ 报 ✓;until read -r l; do :; done → 不报 ✗;while read -r l; do exit; done → 不报 ✗。检测器对注入并非全盘照收,失败集中于 while/until 停止写法一处机制(break_out 仅认 break),修复面集中。按约束,不报者计阻塞项。
未验证事项
- 本报告全部结论基于本地只读命令与 /tmp 合成探针;未运行 CI、未做 REPLAY(审查单元不要求)。
- N5 中
nice/stdbuf未逐个运行时实测(nohup/timeout 已实测 141),归类依同一 exec 包装机制推断。