From df836e4ef7877facd06f1705a83434a2dcbe5f02 Mon Sep 17 00:00:00 2001 From: JianFeeeee Date: Fri, 25 Sep 2026 07:51:36 +0800 Subject: [PATCH] =?UTF-8?q?=E2=98=85=E2=98=85=20pi=20c6f619b8=20=E7=9A=84?= =?UTF-8?q?=20`head=20-132`=20=E5=8D=95=E7=82=B9=E5=8F=98=E5=BC=82?= =?UTF-8?q?=E6=88=90=E7=AB=8B=EF=BC=88=E4=B8=80=E8=A1=8C=E6=89=93=E7=A9=BF?= =?UTF-8?q?=E8=87=AA=E6=A3=80+=E4=B8=8B=E7=95=8C=E5=AE=88=E5=8D=AB?= =?UTF-8?q?=EF=BC=89=E2=87=92=20=E8=A1=A5**=E9=80=90=E6=96=87=E4=BB=B6?= =?UTF-8?q?=E6=8E=A2=E9=92=88**=EF=BC=9B=E2=98=85=20=E4=B8=94=E6=88=91?= =?UTF-8?q?=E5=AE=9E=E6=B5=8B=E5=87=BA=20pi=20=E7=9A=84=E5=BB=BA=E8=AE=AE*?= =?UTF-8?q?*=E6=9C=AC=E8=BA=AB=E6=9C=89=E6=B4=9E**=EF=BC=88=E5=88=A0?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E8=A1=8C=EF=BC=89=E2=87=92=20=E5=86=8D?= =?UTF-8?q?=E5=8A=A0**=E8=A1=8C=E5=8F=B7=E7=AE=97=E6=9C=AF=E4=B8=8D?= =?UTF-8?q?=E5=8F=98=E9=87=8F**=EF=BC=9B=E2=98=85=E2=98=85=20=E5=B9=B6**?= =?UTF-8?q?=E6=92=A4=E9=94=80=E6=88=91=E4=B8=8A=E4=B8=80=E8=BD=AE"?= =?UTF-8?q?=E9=97=AD=E4=B8=8D=E4=BA=86"=E7=9A=84=E8=BE=B9=E7=95=8C?= =?UTF-8?q?=E7=94=B3=E6=8A=A5**=EF=BC=88=E6=88=91=E5=BD=93=E6=97=B6?= =?UTF-8?q?=E6=8A=8A"=E6=8D=A2=E9=9D=A2"=E8=AF=AF=E5=88=A4=E6=88=90"?= =?UTF-8?q?=E5=90=8E=E9=80=80"=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ★ pi 的变异我复现: `strip_text` 尾接 `| head -132`(**一行**)⇒ rc=**0**、打"裸赋值 0 处"(树上注入 1 处) 根因三层: ①自检样本只有 **2 行** ⇒ 长度相关变异对它**恒等** ⇒ 自检通过 ②下界守卫只数"几个文件 source 了" ⇒ source 在 131、违规在 135 ⇒ 保住 source ⇒ 通过 ③正式扫描看不到违规 ⇒ `_had=0 ∧ _cnt=0` ⇒ "一致" ⇒ fail-closed 不触发 窗口精确: 逐个 head -N 实测 **打穿区间 = [131,134],宽 4**(source 行, 动作行) ⇒ 记法(收 pi 的): **"共用同一实现"只在"样本能触发该变异"时才看得见** —— 输入相关变异(head/truncate/长度/locale)对短样本恒等 ⇒ 共用实现反而保证"一起静默" ★ 补法一: **逐文件探针**(拿"真文件 + 首尾已知注入"当样本 ⇒ 样本形状 = 被检对象形状) ★★ 但 pi 的建议只放**一个**探针 ⇒ **我只能抓到反方向的截断**(我逐条实测): 单探针在**尾部**: `head -132` 砍掉它 ⇒ 抓到;`tail -132` 保住它 ⇒ **漏** ⇒ 所以**首尾各一**,且要求 head 行号 < tail 行号(顺带抓 `tac`) ★★★ 补法二(**pi 的建议里没有,是我实测出来的洞**): 首尾两探针**仍然不够** —— 把**中间某一行**(=那条违规行)删掉、首尾都保住 ⇒ 探针全在、顺序也对 ⇒ **假绿** 实测: `awk 'NR!=135'` / `sed '135d'` ⇒ rc=**0** ⇒ 再加**行号算术不变量**: 逐行原位 ⇒ `tail 行号 == head 行号 + 内容行数 + 1` 中间少一行 ⇒ tail **上移** ⇒ 等式不成立 ⇒ 抓到 ⇒ 三条件(两端都在 / 顺序对 / 算术成立)合起来 = "**这个过滤器是逐行原位的**" ★★ 撤销上一轮的边界申报(`2e8d5aa` 那个文件的末尾): 我当时写"§五 闭不了 —— 要闭需一条**独立于本文件**的检查 ⇒ 无限后退 ⇒ 记为边界"。 ⇒ ★ 错在: 我把"独立"理解成"**另一个文件**",而真正需要的是"**另一次观察**"。 逐文件探针仍在**本文件**里,却**不经过自检那条判断**(直接看 `_scan_stripped` 的行号算术)。 ⇒ 实测: 同一 ①∧② 合取,现在 rc=**1**(报"逐文件探针失败")。 ⇒ 记法: **"无限后退"论证要先问"上一层与这一层是不是同一次观察"** —— 换了**观测对象**(已知样本 → 被检文件本身)不是后退,是**换面**。 我把"换面"误判成"后退",于是把一个**可闭**的点申报成了边界。 ⇒ 该段改为"已闭 + 保留历史(因为错法有教学价值)" ⇒ 新增 ⑧ 申报**新探针的射程**: 它证"读入逐行原位",**不**证"注释剥离的语义正确" (探针只查行号与两端,不查"哪一列被删") ★ 变异矩阵(11 向,全部真跑): [0] 注入违规无过滤器 ⇒1 [1] cat ⇒1 [2] ★pi head -132 ⇒1 [3] tail -132 ⇒1 [4] tac ⇒1 [5] ★★删中间 135 行 ⇒1 [6] ★★sed 135d ⇒1 [7] 删中间 400 行 ⇒1 [8] grep -v '^$' ⇒1 [9] sort -u ⇒1 [10] head -c 5000 ⇒1 非恒红: 干净副本(三调用者全合规)⇒ rc=**0**、报"3 个调用者,裸赋值 0 处" ✓ criteria-hygiene **9/9** 绿 ★ 并发: 全程未碰别人的在飞改动(`install.sh` / `criteria-hygiene.test.mjs` 的临时变异 都只在我 `/tmp` 的副本上模拟)。 --- deploy/check-require-declaration.sh | 77 ++++++++++++++++++++++++++--- 1 file changed, 69 insertions(+), 8 deletions(-) diff --git a/deploy/check-require-declaration.sh b/deploy/check-require-declaration.sh index 4796570..d14a9be 100755 --- a/deploy/check-require-declaration.sh +++ b/deploy/check-require-declaration.sh @@ -213,7 +213,61 @@ agentmail_require git go' || true)" unset _nc # 正式扫描:**先 strip 一次**(唯一实现 `strip_text`),两条通道都用这同一份结果。 + # + # ★★ 逐文件探针(2026-09-25 pi 的 `head -132` 单点变异逼出来的,**我实测后加到两个探针**): + # pi 的变异: `strip_text` 尾接 `| head -132` —— **一行改动**同时打穿自检与下界守卫: + # ① 自检样本只有 **2 行** ⇒ `head -132` 对它**恒等** ⇒ 自检照样匹配 ⇒ 通过 + # ② 下界守卫看的是"有几个文件 source 了"⇒ install.sh 的 source 在 **131** 行、 + # 违规动作在 **135** 行 ⇒ `head -132` 保住 source、砍掉违规 ⇒ 仍是 3 个调用者 ⇒ 通过 + # ③ 而正式扫描看不到违规行 ⇒ `_had=0 ∧ _cnt=0` ⇒ "一致" ⇒ fail-closed 不触发 + # ⇒ 实测 rc=**0**,打"裸赋值 0 处"(树上注入着 1 处)⇒ **静默漏** + # ⇒ 打穿窗口 = **[source 行, 动作行) = [131,134],宽 4**(我逐个 head -N 量过) + # ★ 根因(pi 的记法,我收): **"共用同一实现"只在"样本能触发该变异"时才看得见** —— + # 一个**输入相关**的变异(head/truncate/长度阈值/locale)对**短样本**是恒等的, + # 于是"共用实现"反而保证了**两者一起静默**。 + # ★ 补法: **对每个真文件也跑一次探针**(拿"真文件 + 已知注入"当样本), + # 这样"样本的形状"就是**被检对象本身的形状**,长度相关变异无法对它恒等。 + # ⚠️ **我为什么用两个探针而不是一个**(我实测出来的,比 pi 的建议多一步): + # 只放**一个**探针时,它只能抓**从反方向**来的截断 —— + # 探针在**尾部**: `head -132` 砍掉它 ⇒ 能抓;`tail -132` **保住**它 ⇒ 抓不住 + # 探针在**首部**: `tail -132` 砍掉它 ⇒ 能抓;`head -132` **保住**它 ⇒ 抓不住 + # (我逐条实测:单探针在 head/tail 上各漏一半) + # ⇒ 所以**首尾各一个**,并**要求头探针的行号 < 尾探针的行号**: + # 截断必砍掉其中一端;`tac` 之类保内容但**反转顺序**的变换则由"顺序"这条抓。 + # ⚠️ 申报射程: `sort -u` / `grep -v '^$'` 抓不到(它们保留违规行、不改变"有/无"); + # 但实测它们对本文件**不构成假绿**(只可能改变**计数**,而计数类失效由 fail-closed 兜)。 _stripped="$(strip_text "$(cat "$f")")" + # ⚠️⚠️ **只靠首尾两个探针还有一个洞,是我实测出来的(pi 的建议里没有这一步)**: + # 把**中间某一行**(例如那条违规行)删掉、首尾都保住 ⇒ 两个探针**都在**、顺序也对 + # ⇒ 探针通过,而违规行没了 ⇒ **假绿**。实测: `awk 'NR!=135'` / `sed '135d'` ⇒ rc=**0**。 + # ⇒ 补一条**行号算术不变量**: 读入是逐行原位 ⇒ 尾探针行号必须**恰好等于** + # 头探针行号 + 内容行数 + 1(+1 是尾探针自己那一行)。 + # 中间少一行 ⇒ 尾探针**上移** ⇒ 等式不成立 ⇒ 抓住。 + # 实测(`probe` 原型): 干净 `h=1 t=802 n=800`(802 = 1+800+1 ✓); + # `sed 135d` ⇒ `t=801` ≠ 802 ⇒ 抓到;`head/tail -N` ⇒ 缺探针 ⇒ 抓到; + # `tac` ⇒ `h=802 t=1`(顺序反)⇒ 抓到;`grep -v '^$'` / `sort -u` ⇒ `t` 偏离 ⇒ 抓到。 + # ★ 三个条件合起来 = "**这个过滤器是逐行原位的**"(保行数、保顺序、保两端)。 + _probe_in="AGENTMAIL_REQUIRE=\"__AM_P_HEAD__\" +$(cat "$f") +AGENTMAIL_REQUIRE=\"__AM_P_TAIL__\"" + # 内容行数与探针**同一条读取路径**(都经 `printf '%s\n'` 补回被命令替换吃掉的尾换行) + _probe_n="$(printf '%s\n' "$(cat "$f")" | awk 'END{print NR}')" + _probe_out="$(_scan_stripped "$(strip_text "$_probe_in")" || true)" + _ph="$(grep -F '__AM_P_HEAD__' <<< "$_probe_out" | head -1 | cut -d: -f1)" + _pt="$(grep -F '__AM_P_TAIL__' <<< "$_probe_out" | head -1 | cut -d: -f1)" + _pexp=$(( ${_ph:-0} + _probe_n + 1 )) + if [ -z "$_ph" ] || [ -z "$_pt" ] || [ "$_ph" -ge "$_pt" ] || [ "$_pt" -ne "$_pexp" ]; then + printf ' [FAIL] %s 逐文件探针失败:读入这一环**不是逐行原位**的 —— 它在丢弃或重排内容。\n' "$f" >&2 + printf ' head 探针=%s,tail 探针=%s,内容行数=%s(要求 tail == head + 行数 + 1 = %s)。\n' \ + "${_ph:-空}" "${_pt:-空}" "$_probe_n" "$_pexp" >&2 + printf ' ⇒ 三个条件缺一不可: 两端都在(抓 head/tail 截断)、顺序对(抓 tac)、\n' >&2 + printf ' 行号算术成立(抓"只删中间某一行"—— 这一条只靠首尾探针**抓不到**)。\n' >&2 + printf ' ⇒ 这一环静音时,**自检看不出来**(自检样本只有 2 行,长度相关变异对它恒等),\n' >&2 + printf ' 下界守卫也看不出来(它只数"有几个文件 source 了",不看行号)。\n' >&2 + printf ' ⇒ 按红计(绝不能因为两条通道都不说话就当成"没问题")。\n' >&2 + exit 1 + fi + unset _probe_in _probe_out _probe_n _ph _pt _pexp _had=0 grep -qE "$AM_SCAN_RE" <<< "$_stripped" && _had=1 _cnt=0 @@ -254,14 +308,21 @@ exit 0 # ══════════════════════════════════════════════════════════════════════════════ # ①②③ 是"判据读输入之后"的失效;④ 是"哪些对象进入判据";⑤ 是"谁来验验证器"。 # -# ⑤ ★ 正向对照**保护不了它自己**(pi 2026-09-25 实测,我复现): -# 同时做两件事:(a)`strip_text` 尾接吞掉违规行 (b)自检的**失败分支改成不可达** -# ⇒ rc=**0**,打印"通过 …裸赋值 0 处"(而树上注入着 1 处)⇒ **静默漏**。 -# 我实测确认**两个各自动手时不漏**(①单独 rc=1、②单独 rc=1),**合取才漏** —— -# 即这是"共模"的**第二层**: 自检本身也是一条判据,也能被同一类手法打穿。 -# ⚠️ 我**不能**闭它: 要闭需一条**独立于本文件**的检查(例如 install.sh 里旁路断言), -# 而那只是把问题推给"那条断言自己会不会被静默"(无限后退)。 -# ⇒ 记为**边界**,与下面的"间接赋值"同级。 +# ⑤ ~~正向对照**保护不了它自己**~~ ⇒ ★ **已闭(2026-09-25 晚,逐文件探针)**, +# 但保留这段历史,因为**我当时的"闭不了"判断本身是错的**,而错法有教学价值: +# 当时(a)`strip_text` 尾接吞掉违规行 ∧(b)自检失败分支不可达 ⇒ rc=0 静默。 +# 我判"要闭需一条独立于本文件的检查 ⇒ 无限后退 ⇒ 记为边界"。 +# ★ 错在哪: 我把"独立"理解成"**另一个文件**",而真正需要的是 +# "**另一次观察**" —— 逐文件探针(见上文)用**被检对象自己的形状**当样本, +# 它仍在本文件里,却**不经过自检那条判断**(直接看 `_scan_stripped` 的行号算术)。 +# ⇒ 实测: 同一 ①∧② 合取,现在 rc=**1**(报"逐文件探针失败")。 +# ⇒ 记法: **"无限后退"论证要先问"上一层与这一层是不是同一次观察"** —— +# 若新一层换了**观测对象**(从"已知样本"换成"被检文件本身"),它不是后退,是**换面**。 +# 我当时把"换面"误判成了"后退",于是把一个可闭的点申报成了边界。 # ⑥ 间接赋值不判(eval/read/env 注入),只判**字面裸赋值**。 # ⑦ 下界守卫只挡"收到 <3 个调用者";"恰好 3 个但换掉其中一个"不判(需会漂的名单)。 +# ⑧ ★ 逐文件探针的**射程**(实测申报,不是"没想到"): 它证明"读入是逐行原位的", +# 但**不能**证明"注释剥离的语义正确" —— 例如把 `sed 's/#.*$//'` 改成 +# `sed 's/#.*$//'` 之外的**等价但不同**的规则(如只删整行注释)仍可能通过探针, +# 因为探针只查"行号与两端",不查"哪一列被删"。要判它需**按列/按语义**的对照。 # ══════════════════════════════════════════════════════════════════════════════