fix(highlight): ReDoS 检测降级为编辑器提示(实测漏判,不可作拒绝闸门)

上一提交把 isReDoSProne 当作 compileRules 的拒绝闸门,声称"9/9 危险全抓"。
独立复查证伪了这个结论:

## 实测漏判 —— 危险规则仍会进热路径

| pattern | 静态检测 | 实测 |
|---|---|---|
| [(](a+)+$      | 放行 | 30 字符 4065ms |
| (?:a\|a)+$     | 放行 | 26 字符 7547ms |
| ((a\|a))+$     | 放行 | 24 字符 16212ms |
| (\w\|\d)+$     | 放行 | 26 字符 14451ms |

根因三条:外层扫描不跟踪字符类状态([(] 里的 ( 被当组起始)、遇到不闭合
组时 return false 让整个函数交白卷、分支比较只做字面相等抓不到语义重叠。

## 实测误判 —— 合法规则被拒

(?:a{2,3})+ 等把 {n,m} 范围重复当成 {n} 固定重复。

尝试修复后每修一处引入新问题(补字符类状态后又误杀 (?:\d{2})+ 与 ipv4
预设),证明手写正则语法扫描有本质缺陷。

## 三个替代方案均否决(有实测依据)

1. 运行时熔断(循环内查时钟):**原理上无效**。单次 exec 阻塞 28426ms,
   时钟检查根本执行不到
2. re2:不支持 lookbehind,perm/okstate/http 三条预设依赖它
3. Worker + 超时:**可靠**(6/6 危险全抓、806ms 内 tick 51 次主线程未阻塞)
   但往返 0.11ms/块 vs 直接 0.035ms,且终端高亮必须同步(WriteBatcher 消费
   同步染色结果)——只适合规则的异步校验,不适合热路径

## 本次改动

- compileRules 不再因 isReDoSProne 跳过规则:拒绝闸门会静默禁用合法规则
  (已证实会误杀)却仍放进危险规则(已证实会漏判),两头都不对
- isReDoSProne 保留给编辑器提示:漏判不比改动前差,误判只是多一个警告而非
  禁用功能
- 注释与 AGENTS.md 如实标注"只有提示没有根治",并记录三个方案的否决理由,
  避免后来者重复踩坑

热路径的实际边界仍是 MAX_LINE_LEN(>4KB 行不进用户正则)+ MAX_PER_RULE。
真正的解法(Worker 异步校验 + 存盘前把关)列为未完成项。
This commit is contained in:
Bill committed 2026-10-17 23:19:19 +08:00
1 parent c6aca15134
commit 68fa46b2e3
4 files changed
+66 -29

No files matched your search

+30
View File
@@ -179,3 +179,33 @@
- i18n 新文案四语齐备(zh-CN/zh-TW/en/ja,缺译回退简体)
- 渲染层新依赖一律 devDependencies;`@xterm/*` 版本必须与锁定的 6.1.0-beta.304 体系兼容,改依赖后必须真实 `npm install` 并跑 `node scripts/verify-deps.cjs`
- 改动若触碰 AGENTS.md 已记载的行为,同步更新 AGENTS.md
## 最终 review 结论(2026-10-17 收尾)
对 `c6aca15` 做了独立复查,**发现 ReDoS 静态检测不可靠,已从「拒绝闸门」降级为「编辑器提示」**。
### 实测漏判(危险规则仍会进热路径)
| pattern | 静态检测 | 实测耗时 |
|---|---|---|
| `[(](a+)+$` | 放行 | 30 字符 **4065ms** |
| `(?:a|a)+$` | 放行 | 26 字符 **7547ms** |
| `((a|a))+$` | 放行 | 24 字符 **16212ms** |
| `(\w|\d)+$` | 放行 | 26 字符 **14451ms** |
根因:外层扫描不跟踪字符类状态(`[(` 里的 `(` 被当组起始)、遇到不闭合组时整个函数提前返回、分支比较只做字面相等。
### 实测误判(合法规则被拒)
`(?:a{2,3})+`、`(?:\d{2})+` —— 把 `{n,m}` 范围重复当成 `{n}` 固定重复。
### 尝试修复但失败
补字符类状态后又误杀 `(?:\d{2})+` 与 `ipv4` 预设。**每修一处引入新问题**,证明手写正则语法扫描有本质缺陷。
### 否决的三个替代方案
1. **运行时熔断**(循环里查时钟):**原理上无效**。实测单次 `exec` 阻塞 **28426ms**,时钟检查根本执行不到
2. **re2**:不支持 lookbehind,`perm`/`okstate`/`http` 三条预设依赖它
3. **Worker + 超时**:**可靠**(6/6 危险全抓、主线程 806ms 内 tick 51 次未阻塞),但往返 0.11ms/块 vs 直接 0.035ms。**只适合规则异步校验,不适合热路径**
### 当前状态与遗留
`isReDoSProne` 保留为**编辑器提示**(漏判不比改动前差,误判只是多一个警告而非禁用规则)。热路径边界仍是 `MAX_LINE_LEN`(>4KB 行不进用户正则)+ `MAX_PER_RULE`。
**未完成**:真正解法是 Worker 异步校验 + 规则存盘前把关。工作量中等,需单独排期。