agent-exec transport f47b8031e3f17e18db2e03df71cf5c75/45cff96297b4

This commit is contained in:
agent-exec-transport
2026-10-21 02:33:29 +00:00
parent 8803bea966
commit 4e484a547d
@@ -0,0 +1,124 @@
# 独立代码审查:fix/guard-test-blindspot-coverage
- 审查对象:`7a5c80eacb835bfc6e62b3557129ec299bbb5290..8803bea966283996e4287a6f3b883d42f29b8e0b`(分支 `fix/guard-test-blindspot-coverage`,HEAD=8803bea)
- 涉及文件:`tests/test_shell_pipe_hazard.py`、`scripts/e2e-duties.sh`、`scripts/hooks/pre-commit`、`scripts/svc.sh`(另有任务卡 `.task/fix-guard-test-blindspot-coverage.md`,属 host 豁免路径)
- 审查身份:全新独立会话,未参与实现;只读审查,仅本报告文件为新增写产物
- 审查日期:2026-10-20
## 结论:BLOCKED
变更整体是一次严格的覆盖面改进(4 处脚本改写全部语义等价、发现机制消除了硬编码、主流漏检形态已闭合、自检可证伪),但按本单元判定标准「检测器漏检使该缺陷类可回流 = 阻塞」,实测构造出 3 个**此前未被覆盖、运行时确认真实致害(pipefail 下 141)、检测器却不报**的早退读取器形态,其中 `awk nextfile` 属背景中点名的 awk 早退家族成员。故判 BLOCKED;不推翻已完成的等价性与发现机制结论。
---
## 阻塞项
### B1 检测器漏检:`| (head -1)` —— 子壳包裹的早退读取器不被报
- 形态:`seq 1 200000 | (head -1)`。`(` 走 `push_cmd(c)` 入帧(tests/test_shell_pipe_hazard.py:157),帧内 `head -1` 成为**单级管道**,而 `hazards()` 只检查 `stages[1:]`(tests/test_shell_pipe_hazard.py:289-298),故永不命中。
- 运行时确认真实致害:`bash -c 'set -o pipefail; seq 1 200000 | (head -1) >/dev/null'` → exit **141**(/tmp/groundtruth.sh 实测)。
- 检测器实测:`hazards('seq 1 200000 | (head -1)\n')` → `[]`(/tmp/hazard_matrix.py,`subshell-reader` 行:old=miss, new=miss)。
- 影响:head/read/grep -q 全家族均可被一层子壳静默绕过,缺陷类可回流。
### B2 检测器漏检:`awk … nextfile` —— awk 家族成员不被报
- 形态:`seq 1 200000 | awk '$1==7{print; nextfile}'`。`_awk_exits_early` 只识别 `exit`(tests/test_shell_pipe_hazard.py:257-267),`nextfile` 同样会在读完输入前停止读取、关闭 stdin。
- 运行时确认:`bash -c 'set -o pipefail; seq 1 200000 | awk "\$1==7{print; nextfile}" >/dev/null'` → exit **141**。
- 检测器实测:`hazards(...)` → `[]`(矩阵行 `awk-nextfile`:old=miss, new=miss)。
- 影响:背景(PR #664 遗留 a)明确点名 `awk … exit` 为盲区;`nextfile` 是同一 awk 早退家族的常用习语("取首条匹配即停"),修复后仍可无声回流。按判定标准构成阻塞。
### B3 检测器漏检:`| while read … break` —— 安全清单放行了带 break 的危险变体
- 形态:`seq 1 200000 | while read -r l; do break; done`。`early_exit_reader` 首词为 `while`,直接返回 False(tests/test_shell_pipe_hazard.py:270-288 无 while 分支)。
- 运行时确认:`bash -c 'set -o pipefail; seq 1 200000 | while read -r l; do break; done'` → exit **141**。
- 加重因素:新测试把 `seq 1 200000 | while read -r l; do :; done` 列入安全形态(tests/test_shell_pipe_hazard.py:347),未限定"循环体不含 break/exit",等于把危险变体也放行——违反"安全形态清单是否过宽以致把危险写法也放行"的关注点。
- 附带同类(次要、非独立阻塞):`| /usr/bin/grep -q 1`(绝对路径前缀)与 `| exec grep -q 1` 因首词不在前缀剥离清单(tests/test_shell_pipe_hazard.py:273)而漏检,实测 141;词法检测器对动态派发有固有局限,但上述三种均为静态可识别形态。
---
## 已验证通过项(非阻塞)
### 1. 检测正确性(主流形态闭合)
新词法扫描(tests/test_shell_pipe_hazard.py:44-253 `pipelines`)实测判定可靠的形态:
| 形态 | 期望 | 实测 |
|---|---|---|
| heredoc 正文内的管道 | 不报 | 不报(正文按 `<<` 定界符跳过;`heredoc-body-hazard`、`heredoc-unterminated` 均 miss,属正确) |
| heredoc 之后的真实管道 | 报 | 报(`heredoc-then-hazard` → [(4,'grep -q 1')]) |
| case 模式中的 `\|` | 不报 | 不报(`case-pattern-pipe` miss,模式态由 cases 栈维护) |
| case 体内管道读取器 | 报 | 报(`case-body-pipe` → FLAG) |
| 续行后的管道 | 报 | 报(`cont-line-pipe` → [(2,'grep -q 1')]) |
| `$(...)` 嵌套管道 | 报 | 报(`dq-nested-pipe` → [(1,'head -1')]) |
| 反引号 | 报 | 报(`backtick-head` → [(1,'head -1`')],靠词法拆级碰巧覆盖) |
| `$'...'` ANSI 引用 | 正常 | 正常(`ansi-quote` 内 `$(…\|head -1)` 仍报) |
| 花括组读取器 `seq \| { head -1; }` | 报 | 报(`{` 在前缀剥离清单,tests/test_shell_pipe_hazard.py:273) |
| 前缀化读取器 `LC_ALL=C grep -q`、`env … grep -m1` | 报 | 报(单测 tests/test_shell_pipe_hazard.py:324-325) |
对当前安全写法无误报(全 17 个受管脚本套件通过;矩阵误报候选仅 1,见 N1)。
### 2. 发现机制(相对旧硬编码严格变宽)
- `managed_scripts()` 经 `git ls-files -z`(tests/test_shell_pipe_hazard.py:28-33),实测发现 17 个文件,覆盖旧列表全部 6 个 + `scripts/check-pins.sh`、`scripts/evidence.sh`、`scripts/facts-verify.sh`、`scripts/fetch-rdm-pin.sh`、`scripts/hooks/{commit-msg,post-merge,pre-commit}`、`scripts/lint-architecture.sh`、`scripts/plugin-new.sh`、`scripts/zcode/write-guard.sh`。将来新增 `*.sh`/hooks 文件自动纳入,无需改测试。
- `scripts/hooks/*` 被 `:(glob)scripts/hooks/*` 覆盖(含无扩展名脚本);`scripts/**/*.sh` 覆盖 `scripts/zcode/write-guard.sh` 与 `scripts/svc.sh`(零层目录也命中)。
- runtime/ 陈旧克隆不会误扫:`git ls-files` 只列跟踪文件,`runtime/` 在 `.gitignore:9`(`runtime/`),测试断言 `p.startswith("runtime/")` 为空(tests/test_shell_pipe_hazard.py:366),实测通过。
- `EXEMPT = {}`(tests/test_shell_pipe_hazard.py:17)恰当:无静默豁免文件;过期条目守卫(tests/test_shell_pipe_hazard.py:367-368)防止残留。
- 遗留缺口见 N2(非阻塞)。
### 3. 自检有效性
- 新用例断言精确 `(行号, 级文本)`(tests/test_shell_pipe_hazard.py:317-331),词法任何漂移即失败,可证伪。新旧对照实测证明新用例针对真实盲区:`seq 1 200000 | { head -1; }`、`x=\`…\| head -1\``、管道中段 `head`、`awk exit`、`read` 等形态 old=miss → new=FLAG(/tmp/hazard_matrix.py)。
- 全套件实测通过:`PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_shell_pipe_hazard -v` → Ran 6 tests, OK。
- 反向注入验证(本审查构造):见阻塞项 B1/B2/B3——检测器对这三种新构造形态确实不报(这正是判 BLOCKED 的依据)。
### 4. 脚本改写语义等价(4/4 通过)
以 /tmp/equiv.sh 对旧/新写法做同输入探针(输出+退出码):
| 改写 | 位置 | 探针结果 |
|---|---|---|
| `ps … \| grep -Fq` → `grep -Fq < <(ps …)` | scripts/e2e-duties.sh:87 | 活 PID/死 PID/匹配 PID 三态新旧一致(no/no、yes/yes);`<()` 状态不入判定,`-F -q --` 参数不变 |
| `curl … \| head -c 160` → `head -c 160 < <(curl …)` | scripts/e2e-duties.sh:128 | 短体(abc)、长体(1000B 取前 160B)、生产者失败三态新旧一致(含 `st=0`,替代捕获状态取 head 而非管道,此处状态本就未被消费) |
| `echo "$branch" \| grep -qE` → `grep -qE <<<"$branch"` | scripts/hooks/pre-commit:9 | 9 组分支名(合法/非法/空/含空格/`-n pwned`/含引号)退出码逐一相同;here-string 尾随换行与 echo 一致;拒绝文案与 admit 后续执行不变 |
| `"$pgbin/postgres" --version \| awk … exit \|\| true` → `awk … < <(postgres --version) \|\| true` | scripts/svc.sh:4360 | 正常版本(24.1)、二进制缺失(空)、非零退出(空)三态新旧捕获值一致;`pgver` 的 `\|\| true` 保留使替换状态恒 0,与旧 `管道 \|\| true` 等价;awk 提前退出改由 `<()` 吸收 SIGPIPE,不影响替换状态 |
三个被改脚本 `bash -n` 全部通过;均为 `#!/usr/bin/env bash`,`<()`/`<<<` 合法。
### 5. 范围与写守卫
- 变更范围:`git diff --stat` 仅 4 个目标文件 + 任务卡(`.task/**` 为 host 豁免路径),无越界。
- `scripts/hooks/pre-commit` 属写路径守卫:改动仅把 `echo | grep` 换成等价 here-string(探针见上表),正则、拒绝分支、后续 `admit` 调用原样(scripts/hooks/pre-commit:16);shebang 为 bash,here-string 不引入 sh 兼容风险;守卫未被削弱。
---
## 非阻塞建议
- **N1 误报**:`seq 1 200000 | awk '{print "exit"}'` 被误报(`AWK_EXIT` 对 awk 程序文本做裸 `\bexit\b`,tests/test_shell_pipe_hazard.py:259,267),运行时实测 exit=0 安全。当前 17 个受管脚本无触发,仅造成未来开发摩擦(偏安全方向);建议 `_awk_exits_early` 像剥 END 块一样剥离引号串后再查 `exit`。
- **N2 发现缺口(盲区 b 的残留)**:`scripts/` 下既有无扩展名 bash 脚本 `scripts/agent-exec`、`scripts/dm`、`scripts/task`(均 `#!/usr/bin/env bash`)不被 `:(glob)scripts/**/*.sh` 或 `:(glob)scripts/hooks/*` 命中;`docker/entrypoint.sh`(bash,`set -euo pipefail`)亦不在 EXTRA。旧硬编码列表同样未含它们,无回归、人工抽查未见多级管道读取器,但"新增受管脚本不被扫描"对这一命名惯例仍成立。建议增补 `:(glob)scripts/*`(及按需 `:(glob)docker/*.sh`);另 `:(glob)scripts/hooks/*` 不含 hooks 子目录。
- **N3 词法小疵**:`$((a|b))` 算术中的 `|` 会被当作管道拆级(操作数非命令词故暂无误报;若变量名为 `head`/`read` 等且参与按位或可致误报)。可忽略。
- **N4**:安全清单中 `while read` 条目建议加注释/用例说明"仅限循环体无 break/exit",与 B3 修复联动。
---
## 已运行的验证命令与结果(均只读;合成脚本全部位于 /tmp,仓库内无残留)
| # | 命令 | 结果 |
|---|---|---|
| 1 | `PYTHONDONTWRITEBYTECODE=1 python3 -m unittest tests.test_shell_pipe_hazard -v` | Ran 6 tests, OK |
| 2 | `git diff --stat 7a5c80e..8803bea` | 5 文件(4 目标 + 任务卡),+198/−33 |
| 3 | `git ls-files -z -- ':(glob)scripts/**/*.sh' ':(glob)scripts/hooks/*' …` | 17 文件(含 3 个 hooks 无扩展名脚本,无 runtime/) |
| 4 | 新旧检测器对照 `/tmp/hazard_matrix.py`(import 仓库模块与 `git show 7a5c80e:tests/…` 提取的旧模块,26 形态) | 新检 12 形态 FLAG;漏检 4(B1/B2/B3/绝对路径 grep);误报 1(N1);安全形态全部正确放行 |
| 5 | 运行时基线 `/tmp/groundtruth.sh`(bash pipefail) | `(head -1)`、`awk nextfile`、`while break`、`/usr/bin/grep -q` → **141**;`mapfile`/`sed -n 1p`/`xargs -n1`/`awk '{print "x"}'` → 0 |
| 6 | 等价性探针 `/tmp/equiv.sh`(pre-commit 9 分支名、cleanup 3 态、head -c160 4 态、pgver 4 态) | 全部新旧一致(1 行 DIFF 为探针自身期望值笔误,/bin/postgres 本机不存在,新旧同为空) |
| 7 | `bash -n scripts/{e2e-duties,svc}.sh scripts/hooks/pre-commit` | 全部通过 |
| 8 | 受管脚本 `nextfile`/`\| (`/`\| while`/`\| mapfile` 抽查(`git grep`) | 无 nextfile、无子壳读取器;6 处 `| while read` 循环体仅 `continue`(安全),1 处 `exit 2` 为函数用法错误分支 |
| 9 | `head -1` 各被改脚本 / `git grep` 无扩展名 scripts/ 脚本 | shebang 均 bash;`scripts/{agent-exec,dm,task}` 为 bash 脚本(N2 依据) |
| 10 | `git check-ignore -v runtime`、`.gitignore` | `.gitignore:9 runtime/`(ignore 生效,陈旧克隆不入 ls-files) |
## 判定标准对照
- 检测器漏检使该缺陷类可回流:**成立(B1/B2/B3,运行时确认 141)→ 阻塞**。
- 误放行危险写法:安全清单 `while read` 未限 break 变体(B3 加重因素)。
- 改写引入语义回归:未发现(4/4 等价)。
- 写守卫被削弱:未发现。
- 风格与可维护性:N1–N4,非阻塞。