1
0
Files
DiAL/openspec/changes/http-checker-quality-hardening/design.md
lanyuanxiaoyao 76b47006fe feat: 新增两个 OpenSpec 变更提案 — 前端架构重构与 HTTP Checker 质量加固
- frontend-architecture-refactor: 拆分 hooks/组件、类型筛选器动态化
- http-checker-quality-hardening: ReDoS 防护、failure 格式修正、测试补全
2026-05-13 18:40:08 +08:00

5.6 KiB
Raw Blame History

Context

HTTP checker 是 DiAL 拨测系统的核心 runner 之一,负责对 HTTP 目标执行请求并校验响应。经审查发现以下质量问题:

  1. actual 值截断格式不符合 specspec 要求 failure 中的 actual 摘要需截断并附带字符计数,但当前 truncateActual 函数只加省略号无计数,导致用户无法判断原始响应体规模。
  2. ReDoS 风险:用户配置的 regex body 规则和 match operator 直接对大响应体执行 new RegExp().test(),恶意或不当正则可能导致 CPU 阻塞。
  3. JSON 重复解析:多条 json body 规则各自独立调用 JSON.parse(body),对大 JSON 响应体造成不必要的重复开销。
  4. CSS 规则分支冗余checkCssRule 中"无 operator 时检查元素存在"和"exists: true"是重复逻辑。
  5. 重定向测试不足303、307/308、相对路径 Location 等分支缺少测试覆盖。

当前代码结构:

  • src/server/checker/expect/failure.ts — failure 构造函数
  • src/server/checker/runner/http/body.ts — body 规则检查
  • src/server/checker/runner/http/execute.ts — HTTP 执行主流程
  • src/server/checker/expect/operator.ts — operator 匹配逻辑

Goals / Non-Goals

Goals:

  • 实现 failure actual 值截断,满足 spec 要求
  • 消除 regex 相关的 ReDoS 风险
  • 优化多条 JSON 规则的解析性能
  • 精简冗余代码分支
  • 补全重定向和集成测试覆盖

Non-Goals:

  • 不改变 CheckResult / CheckFailure 的类型结构(截断在构造时完成,对外接口不变)
  • 不引入新依赖
  • 不改变 HTTP checker 的功能行为(纯内部质量改进)
  • 不添加 response timing 分段记录(暂缓)
  • 不添加重试机制(拨测场景下重试会掩盖网络问题信号)

Decisions

Decision 1: actual 截断在 mismatchFailure 构造点统一实施

选择:在 expect/failure.tsmismatchFailure 函数内部对 actual 参数截断,阈值 200 字符。

替代方案

  • 在存储层store.ts insertCheckResult截断 — 但这样 API 实时返回的 failure 仍然很大
  • 在每个调用点手动截断 — 分散且容易遗漏

理由:构造点截断是最集中的拦截位置,所有 mismatch failure 都经过此函数一处修改全局生效。expected 值不截断(来自用户配置,通常很短)。

截断格式<前 200 字符>…(共 N 字符) — 保留前缀便于诊断,附带总长度便于判断规模(省略号为单字符 U+2026

Decision 2: ReDoS 防护使用正则复杂度静态检测

选择:在启动期 validate 阶段对 regex body 规则和 match operator 进行静态复杂度检测,拒绝含有嵌套量词等危险模式的正则。运行期不做额外防护。

替代方案

  • 运行期用 AbortSignal + setTimeout 强制中断 — Bun 的 RegExp 执行不可中断,无法实现
  • 使用 safe-regex 库 — 引入新依赖,违反项目规范
  • 限制正则执行的输入长度 — 会影响正常大响应体的匹配

理由:自行实现轻量级检测函数,检查常见 ReDoS 模式(嵌套量词 (a+)+、重叠交替 (a|a)*)。在 validate 阶段拒绝危险正则,比运行期防护更可靠——配置错误应该在启动时暴露。

检测规则

  • 嵌套量词:量词内包含量词(如 (a+)+(a*)*(a+)*
  • 重叠字符类交替后跟量词:(x|x)+ 模式

Decision 3: JSON parse 结果缓存在 checkBodyExpect 层

选择:在 checkBodyExpect 函数中,首次遇到 json 规则时执行 JSON.parse,将结果缓存并传递给后续 json 规则复用。

实现方式:修改 checkSingleBodyRule 签名,接受可选的 parsedJson 参数;在 checkBodyExpect 循环中维护一个 let parsedJson: { ok: boolean; value?: unknown; error?: string } 状态。

理由:改动最小,不改变外部接口,只在内部传递缓存。对于非 json 规则contains、regex、css、xpath无影响。

Decision 4: CSS 规则分支合并策略

选择:将 checkCssRule 重构为线性流程:

  1. 解析 HTML
  2. 处理 exists: false(元素不存在即通过)
  3. 查找元素(不存在则失败)
  4. 处理 exists: true(到这里已确认存在,直接通过)
  5. 提取值attr 或 text
  6. 无 operator 时检查值非 undefined 即通过
  7. 有 operator 时执行匹配

理由:消除当前三层嵌套判断中的重复逻辑,使控制流线性化,更易理解和维护。

Decision 5: execute.ts 提前 duration 检查保留但加注释

选择:保留第 56-74 行的提前 duration 检查逻辑(它是有效的性能优化——避免读取注定超时的 body但重构为独立的 helper 函数使意图更明确。

理由:删除它会导致超时场景下仍然读取完整 body 后才报错,浪费网络带宽和时间。提取为 checkEarlyTimeout 函数名即可自解释。

Risks / Trade-offs

  • ReDoS 静态检测的误报:过于严格的检测可能拒绝合法但看起来复杂的正则。→ 缓解:只检测最常见的嵌套量词模式,不做过度分析;提供清晰的错误信息指导用户修改。
  • actual 截断丢失诊断信息:截断后用户无法看到完整 actual 值。→ 缓解200 字符的前缀通常足够定位问题;如需完整响应体,用户应直接请求目标 URL 查看。
  • JSON parse 缓存的内存占用:对于大 JSON 响应体,缓存的 parsed 对象会在整个 body rules 检查期间驻留内存。→ 缓解:这是短暂的(单次检查周期内),且原本每条规则都会各自 parse 一份,缓存反而减少了峰值内存。