修复: **对照能被"按内容分流"的通道骗过** —— 红必须是"报出来的"不是"退出来的"(层 10)

pi 2026-09-18 报的层 10。**成立,我复现,读数与它逐字相同。**
**同意 pi:这一串到层 10 为止,不再往上找。**

## 一、缺陷:对照只问"输入→输出是否非平凡",没问"那个输出是不是被守对象产生的"

pi 的构造:快入口**读自己的源码**、发现"我被改坏了"就 `process.exit(1)`。
它在**两种输入下都答对**(原样走真实现 ⇒ 绿;被改坏 ⇒ 自己退 1)⇒
上一版那条"对照"(未改坏必须 rc=0)**通过**,而层 7 的假体照旧静默。

实测:**`red=9`、红清单 diff 0 行** —— 与 pi 的读数逐字相同。

★ 根因:**对照只问"输入→输出映射是否非平凡",没问"那个输出是不是被守对象产生的"。**
通道可以**自己**产生那个"红"(靠检测自己的源码),于是它既是仪器又是被测者 ——
层 9 是"仪器归被测者管",层 10 是"**仪器可以自己造假象,而它造得让对照满意**"。

## 二、修法:把"红"从退出码改成可归因的报告

· ① 干净孩子必须 **rc=0 且 0 条 `RED ` 行**(健康的自检不该喊红);
· ② 改坏后的孩子必须 **rc=1 且 ≥1 条 `RED ` 行**(它得说出**为什么**)。
· ⇒ 只会 `process.exit(1)` 的通道**什么也没说** ⇒ ①过②不过 ⇒ **红**。

实测 pi 的分流通道 + 假 ok×13 ⇒ **红**:
`因果判据的红不是报出来的:exitcode-selftest 改坏后 rc=1 却一条 RED 行都没有`

★ 一般化:**"它红了吗"必须落在"它说了什么"上** ——
退出码是**任何人**都能给的(连崩了都给),而 `RED ` 行是被守对象**自己的判断**。

## 三、⚠️ 残留:通道打一行假 `RED ` 仍能过

我实测了:通道把 `process.exit(1)` 换成
`console.log('RED 假报告…'); process.exit(1)` ⇒ 同样两问都答对、`red=9`、0 条因果红。
**成本只高了一行 `console.log`。** 这与 pi §二的根因完全同形:
**通道能分辨两种输入,就答对两问。**

· pi 的 ① 方向(改坏**孩子读的外部依赖**)能穿透这个分流 —— 但我实测:
  **只有 `exitcode-selftest` 读外部 `summary.py`**;另 4 条的生产符号
  (`probeIsDue`、`parseSkips`、`verdictOf`、`summarizeMutants`)都在 `run-all.mjs` **自己里**。
  ⇒ **① 不 generalize**,它只堵 1/5。
· pi 的 ②(加第三种输入"改坏无关符号")也测了:**定向通道**(只认自己那一个改坏形态)
  在无关符号被改坏时仍走真实现 ⇒ rc=0 ⇒ **② 通过** —— 被绕过。

## 四、定价与停止(§6.1.5)

层 5→10 六轮,每轮都真实、每轮读数都硬,但**边际收益在递减**:
| 层 | 攻击成本 | 修法后剩余 |
|---|---|---|
| 5/6 | 一处编辑 | 同步删多处仍静默 |
| 7 | 写假体 | 锚点到表外 |
| 8 | 删表 | 新自检漏配因果对被②抓 |
| 9 | 桩化通道 | 通道恒 rc=1 被对照抓 |
| 10 | 读自己源码的分流通道 | **打一行假 RED 仍过**(成本 +1 行) |

★ 三个结论:
1. **校准价值在层 7~9 已拿满** —— 层 10 只是在**重复同一课**。
2. **真世界里没有攻击者会这样改代码**:它的威胁模型要求攻击者同时改生产符号
   + 通道 + 假体 + 假 RED 行 —— 这与"直接删契约行"的真实风险等级相距太远。
3. **留下的东西是可复用的**(§6.1.4 的判据 + §6.1.3 的"每加一条防线回答两问"),
   **其余的建议停在这里**:不承认它封死,也不再往上爬。

⇒ **同意 pi:这一串到层 10 为止。** 注意力转回到到期那 6 条判据的升级。

## 五、变异验证(都已还原)

| 变异 | 结果 |
|---|---|
| 层 10 pi 的分流通道 + 假 ok×13(修复前)→ **复现** | `red=9`、红清单 diff 0 行 |
| 层 10 pi 的分流通道 + 假 ok×13(**修复后**) | **红**:`rc=1 却一条 RED 行都没有` |
| 假 RED 行通道 + 假体(残留) | `red=9`、0 条因果红 —— **残留,已登记** |
| 通道恒 rc=1(层 9 的对照) | 红红 5 条(未被破坏) |
| 回归 层6/7/8/5/契约行删掉/M43/M45/M47/M48 | 各 1 条 |
| **`CAUSAL` 5 对逐条直验**(含新可归因判据) | 改坏后**每条** rc=1 |
| 干净树假红检查 | `^RED` **0 条**、对照红 0、不可归因红 0 |

## 六、状态

· 五个自检单独跑全 rc=0;默认跑 `checks=462 pass=458 fail=4 skip=0 red=9 broken=0 unreported=0 verdict=red`、
  `mutants=48 ran=47 skipped=1 on_new_criteria=35 diag=none baseline=7/7✓`;正常态 `^RED ` **0 条**。
· 本提交含 `client/electron/test/run-all.mjs`(可归因判据)与
  `client/electron/test/CRITERIA.md`(新增 §6.1.4 + §6.1.5)。
· 提交前 `HEAD=5afb2c1`。
This commit is contained in:
2026-09-18 09:18:06 +08:00
parent 5afb2c1550
commit 7ca6ae14c6
2 changed files with 97 additions and 0 deletions

View File

@ -342,6 +342,70 @@ T 全桩 + 假 ok×13(层 7) ⇒ red=9、**红清单 diff 0 行**(层 7
★ 层级表:写法(4) → 存在性(5,它还在吗) → 行为(6,它做了吗) → 自述(7,被测者可信吗)
→ 锚表(8,锚还在吗) → **观测(9,判断据的那个人可信吗)**。
### 6.1.4 **对照能被"按内容分流"的通道骗过** —— 红必须是"报出来的",不是"退出来的"(pi 2026-09-18,层 10)
pi 报的层 10:上一版那条"对照"(未改坏必须 rc=0)能被一个**读自己源码**的通道骗过。
通道在快入口里加:读自己的源码、发现"我被改坏了"(源码里出现了改坏标记)就 `process.exit(1)`。
它在**两种输入下都答对**:原样走真实现 ⇒ 绿;被改坏 ⇒ 自己退 1 ⇒ **对照通过**,
而层 7 的假体照旧静默。实测:**红清单 diff 0 行**。
★ 根因:**对照只问"输入→输出映射是否非平凡",没问"那个输出是不是被守对象产生的"。**
通道可以**自己**产生那个"红"(靠检测自己的源码),于是它既是仪器又是被测者 ——
层 9 是"仪器归被测者管",层 10 是"**仪器可以自己造假象,而它造得让对照满意**"。
**修法:把"红"从退出码改成可归因的报告。**
· ① 干净孩子必须 **rc=0 且 0 条 `RED ` 行**(健康的自检不该喊红);
· ② 改坏后的孩子必须 **rc=1 且 ≥1 条 `RED ` 行**(它得说出**为什么**)。
· ⇒ 只会 `process.exit(1)` 的通道**什么也没说** ⇒ ①过②不过 ⇒ **红**。
★ 一般化:**"它红了吗"必须落在"它说了什么"上** ——
退出码是**任何人**都能给的(连崩了都给),而 `RED ` 行是被守对象**自己的判断**。
⚠️ **已知残留(不声称层 10 封死)**:通道打一行**假 `RED `**(`console.log('RED …')`)
⇒ 同样两问都答对。实测 `red=9`、0 条因果红。**成本只高了一行 `console.log`。**
⇒ 这与 pi §二的根因完全同形:**通道能分辨两种输入,就答对两问。**
· pi 的 ① 方向(改坏**孩子读的外部依赖**而非孩子自己的源码)能穿透这个分流 ——
但我实测:**只有 `exitcode-selftest` 读外部 `summary.py`**;另 4 条的生产符号
(`probeIsDue`、`parseSkips`、`verdictOf`、`summarizeMutants`)都在 `run-all.mjs` **自己里**。
⇒ **① 不 generalize**,它只堵 1/5。
· pi 的 ②(加第三种输入"改坏无关符号")也测了:**定向通道**(只认自己那一个改坏形态)
在无关符号被改坏时仍走真实现 ⇒ rc=0 ⇒ **② 通过** —— 被绕过。
⇒ ★ **这一串的底(层 5→10)**:每加一层,成本从"一处编辑"抬到更高,
但"多处同步删仍静默"和"通道能分辨输入就答对两问"**性质没变**。
**防线是会长出新的被守对象的,而观测通道最终都能分辨输入。**
⇒ pi 建议到此为止(层 10),我同意 —— 见 §6.1.5。
★ 层级表:写法(4) → 存在性(5) → 行为(6) → 自述(7) → 锚表(8) → 观测(9)
→ **归因(10,"红"是报出来的还是退出来的)**。
### 6.1.5 **这一串的定价:为何到层 10 为止**(pi 2026-09-18,dsh 同意)
层 5→10 六轮,每轮都真实、每轮读数都硬,但**边际收益在递减**,且有实测依据:
| 层 | 攻击成本 | 修法后剩余 |
|---|---|---|
| 5/6 | 一处编辑 | 同步删多处仍静默 |
| 7 | 写假体 | 锚点到表外 |
| 8 | 删表 | 新自检漏配因果对被②抓 |
| 9 | 桩化通道 | 通道恒 rc=1 被对照抓 |
| 10 | 读自己源码的分流通道 | **打一行假 `RED ` 仍过**(成本 +1 行) |
★ 三个结论(都实测过):
1. **这一串的价值是校准口径**("锚点必须落在被守对象管不着的地方"),
这个价值在层 7~9 已拿满 —— 层 10 只是在**重复同一课**。
2. **真世界里没有攻击者会这样改代码**:它的威胁模型要求攻击者
同时改生产符号 + 通道 + 假体 + 假 RED 行 —— 这与"直接删契约行"的
真实风险等级相距太远。
3. **留下的东西是可复用的**(§6.1.4 的判据 + §6.1.3 的"每加一条防线回答两问"),
**其余的建议停在这里**:不承认它封死,也不再往上爬。
⇒ **同意 pi:这一串到层 10 为止,不再往上找。** 注意力转回到
到期那 6 条判据(`harmony-appearance`、`harmony-logic`、`cross-client-theme`、
`appearance-defaults`、`harmony-admin`、`harmony-imageprep`)的升级。
★ 层级表(**每层都是一根轴**):写法(4) → 存在性(5,"它还在吗") → 行为(6,"它做了吗")
→ **自述(7,"它说的话可信吗")**。

View File

@ -2752,9 +2752,42 @@ let selfCheckRuns = 0;
reds.push(`(自检)因果对照失败:\`${name}\` 未改坏时 rc=${base.status}(通道坏了)`);
continue;
}
/*
* ★★★ **层 10 的修法:红必须是"报出来的",不是"退出来的"**(pi 2026-09-18 报)。
*
* pi 的构造:快入口**读自己的源码**、发现"我被改坏了"就 `process.exit(1)`。
* 那个通道在**两种输入下都答对**(原样走真实现 ⇒ 绿;被改坏 ⇒ 自己退 1)⇒
* 上一版那条"对照"(未改坏必须 rc=0)**通过**,而层 7 的假体照旧静默。
* ⇒ 根因:**对照只问"输入→输出是否非平凡",没问"那个输出是不是被守对象产生的"。**
*
* ⇒ 修法:**把"红"从退出码改成可归因的报告** ——
* ① 干净孩子必须 **rc=0 且 0 条 `RED ` 行**(健康的自检不该喊红);
* ② 改坏后的孩子必须 **rc=1 且 ≥1 条 `RED ` 行**(它得说出**为什么**)。
* ⇒ 只会 `process.exit(1)` 的通道**什么也没说** ⇒ ①过②不过 ⇒ **红**。
* ⇒ 而且它**不能靠常量骗过两问**:常量 rc=1 挂在①、常量 RED 也挂在①。
* ★ 一般化:**"它红了吗"必须落在"它说了什么"上** ——
* 退出码是**任何人**都能给的(连崩了都给),而 RED 行是被守对象**自己的判断**。
* ⚠️ 已知残留(见 `CRITERIA.md` §6.1.4):通道**同时**伪造"0 条 RED + rc 0"与
* "≥1 条 RED + rc 1"仍然能过 —— 但那已经等于**重新实现那条自检的检测**,
* 代价远高于它替代的东西。**我不声称层 10 封死。**
*/
const baseReds = (base.stdout || '').split('\n').filter(l => l.startsWith('RED ')).length;
if (baseReds !== 0) {
console.log(`RED 因果判据的**干净孩子说了红**:\`${name}\` 未改坏时打了 ${baseReds} 条 \`RED \``
+ ' ⇒ 这个读数不能归因给改坏(它平时就在喊红)');
reds.push(`(自检)因果对照失败:\`${name}\` 未改坏时已有 ${baseReds} 条 RED`);
continue;
}
writeFileSync(target, src.replace(from, to));
const got = spawnSync(process.execPath, [target, `--only-selftest=${name}`],
{ encoding: 'utf8', cwd: ROOT });
const gotReds = (got.stdout || '').split('\n').filter(l => l.startsWith('RED ')).length;
if (got.status === 1 && gotReds === 0) {
console.log(`RED 因果判据的**红不是报出来的**:\`${name}\` 改坏后 rc=1 却**一条 \`RED \` 行都没有**`
+ ' ⇒ 这个"红"可能是**观测通道自己退的**(或它崩了),不是那条自检判出来的');
reds.push(`(自检)因果判据:\`${name}\` 改坏后 rc=1 但**没有 RED 行**(红不可归因)`);
continue;
}
if (got.status === 0) {
console.log(`RED 把 \`${name}\` 守的生产符号改坏之后,它**仍然全绿**`
+ ' ⇒ 它没在读那个符号(或它是个空壳)');