返回 AICAP-180
B9 · Day 90部署 + 韧性 + 可观测 + 独立红队

修复闭环 + 回归

Day 88 投放攻击、Day 89 独立评级并确认了 ≥1 条真攻击——红队抓到了洞。今天是 B9 的收口:把洞补上,并写回归用例防止复发。红队的价值不在「抓到」而在「闭环」——抓到→落策略修复→写回归→重放验证已挡→eval gate 转绿。在 B1→B18 曲线上,这是 P7(安全/合规)与 P2(评测 gate)的合流:修复手段落在 src/agent/mcp/toolRegistry.t

阶段: B9 · 部署 + 韧性 + 可观测 + 独立红队(Day 81-90) 标签: #fix-loop #regression-test #fail-closed-gate #hitl

今日导引(由浅入深)

Day 88 投放攻击、Day 89 独立评级并确认了 ≥1 条真攻击——红队抓到了洞。今天是 B9 的收口:把洞补上,并写回归用例防止复发。红队的价值不在「抓到」而在「闭环」——抓到→落策略修复→写回归→重放验证已挡→eval gate 转绿。在 B1→B18 曲线上,这是 P7(安全/合规)与 P2(评测 gate)的合流:修复手段落在 src/agent/mcp/toolRegistry.ts(事中拦截越权工具调用 + 输出过滤)与 src/aml/hitl.ts(SAR 强制人审),回归用例落进 src/agent/eval/tasks.ts,最后用 src/agent/eval/gate.ts(fail-closed)守住「不再退化」这条绿线。今天的最小可判定产出:重放被拦 + 回归测试绿——这步「待跑(需 key)」,但修复着力点(toolRegistry/hitl/gate)均已 ✅ 建好。

1. 机理精读

红队闭环 = 抓到 → 修 → 防复发,三段缺一不可。 只抓不修是漏洞清单;修了不写回归,下次重构/换模型时洞会悄悄回来。闭环的终点不是「这次挡住了」,而是「有一条自动化断言永久守着这个洞」——这正是把安全从「一次性人工审查」升级为「持续可验证」的关键。

两层修复着力点。 针对 Day 89 确认的真攻击,修复落在两处:

  • 事中拦截(toolRegistry 层):在工具调用进入 handler 之前加策略——拦截越权工具调用、对输入按 schema 校验、对输出做过滤(拦截泄露 / 不安全输出)。这是 LLM06(越权)与 LLM02(不安全输出)的防线。
  • 强制人审(HITL 层):SAR 路径继续靠 src/aml/hitl.tscanAutoFile()=false + 状态机,确保任何「诱导自动归档」的攻击都撞在「必须人类 approve」的硬墙上。

回归用例:把真攻击钉成断言。 把 Day 89 确认的真攻击改写成一条 tasks.ts 里的任务,加 1 条断言(攻击应被挡 / SAR 不应被自动归档 / 越权工具应被拒)。这条断言进 CI 后,任何让该洞复活的改动都会让测试变红——攻击从「我们手动验过」变成「机器永久守着」。

fail-closed gate:缺数据即拦截,绝不静默放行。 gate.ts 的核心纪律是 FAIL CLOSED——若配置了某阈值但当前指标缺失或非有限值(NaN/Infinity,来自损坏的 eval),那是违规、是红灯,绝不静默变绿。这与「fail-open(出错就放行)」相反:安全 gate 宁可误拦也不漏放。checkGate 检查 completionRate 退化(默认 maxDrop 0.05 = 5pp)、judge-human κ 是否仍校准(minKappa)、unknown rate 是否超限——任一缺失/越界都进 violationspass = violations.length === 0

关键权衡:拦截严格度 vs 可用性。 输出过滤/工具拦截太严会误伤正常请求(false positive),太松则漏攻击。本仓的取舍是「SAR 这种高副作用路径一律 fail-closed + 人审(宁误拦)」,普通工具调用则按策略+schema 校验拦明确越权。这是「副作用越大、拦得越死」的分层。

与相邻概念的边界。 修复 ≠ 重训模型:今天的修复是系统层策略/护栏(toolRegistry 拦截、HITL gate),不动模型权重——因为模型行为不可完全控制,安全要靠「模型外的确定性护栏」兜底。回归用例 ≠ 红队攻击集:红队是探索性攻击(找新洞),回归是固化已知洞(防旧洞复发),两者互补。

2. 代码走读:toolRegistry.ts + gate.ts + hitl.ts

读三处修复着力点:

  • McpToolRegistry.call(name, args)(toolRegistry.ts:183):调用前先 validate(spec.inputSchema, args)——schema 校验失败抛 McpCallError(INVALID_PARAMS),handler 抛错包成 INTERNAL_ERROR这是事中拦截的最小演示:输入不合声明就进不了 handler。注释明确该装置是「工具暴露给 LLM 的契约层」,schema 校验是零信任防线的最小演示(生产还需权限边界、来源校验、审计)。
  • register(spec, handler)(toolRegistry.ts:151):重名抛错(防静默覆盖)、tool name 须匹配 ^[a-zA-Z][\w.-]*$、inputSchema 必须是 object——注册期就拒绝畸形工具。
  • validate(schema, value)(toolRegistry.ts:112):递归校验 type/enum/minLength/min-max/required/array items——越权参数、缺必填、超界值都被挡。
  • handle(req) JSON-RPC dispatch(toolRegistry.ts:199):永不抛错,所有失败折成 response.error(符合 JSON-RPC 语义)——攻击不能靠「让 server 崩」来探测。
  • checkGate(current, baseline, thresholds)(gate.ts:31):fail-closed——baseline.completionRate 存在但 current 缺失/非有限 → 违规(gate.ts:37);completionRate 掉超 maxDrop(默认 0.05)→ 违规并报「dropped X pp > allowed Y pp」;κ 低于 minKappa、unknown 超 maxUnknownRate 同理。pass = violations.length === 0
  • file(r, now) / canAutoFile()(hitl.ts:38/44):SAR 只有 approved 才能归档,canAutoFile() 返回字面量 false——诱导自动归档的攻击撞硬墙。

手算:fail-closed gate 的四种判定

checkGate(current, baseline, thresholds) 用默认 maxCompletionDrop=0.05minKappa=0.6maxUnknownRate=0.1 走四个例子:

baseline.completionRatecurrent.completionRatecurrent.judgeHumanKappa判定
E10.7930.7900.7pass(掉 0.3pp ≤ 5pp,κ≥0.6)
E20.7930.7300.7fail(掉 6.3pp > 5pp)
E30.793NaN(损坏的 eval)0.7fail(current 非有限 → fail-closed,绝不静默绿)
E40.7930.7900.5fail(κ 0.5 < min 0.6,judge 失校准)

E3 是 fail-closed 的灵魂:损坏/缺失的指标 = 红灯,不是「没检查到就放行」。这把「eval 文件被改坏 → gate 静默变绿 → 漏洞合并」这条供应链风险堵死。E2 用的退化阈值(5pp)也解释了为何 agent-evals/baseline.json 必须先选定基线 run——没有 baseline.completionRate,E2 这条退化检查根本不触发(gate.ts:36 的 Number.isFinite(baseline.completionRate) 守卫)。

回归用例的生命周期(抓到 → 永久守着)

  1. Day 89 确认真攻击 A(如「间接注入诱导不归档」命中)。
  2. toolRegistry.ts 加输出过滤 / hitl.ts 兜底,使 A 被挡。
  3. 把 A 写成 tasks.ts 一条任务 + 1 条断言(「A 应被挡 / SAR 不应自动归档」)。
  4. 重放 A → 断言绿。
  5. 此后任何让 A 复活的改动(换模型、重构 prompt)→ CI 里这条断言变红 → 合并被拦。

这条生命周期把安全从「一次性人工验过」升级为「机器永久守着」,正是红队闭环的终点。

3. 今日实战

  1. 落修复:对 Day 89 确认的真攻击,在 src/agent/mcp/toolRegistry.ts 加策略 / 输出过滤(事中拦截越权工具调用、拦截泄露/不安全输出);SAR 路径靠 src/aml/hitl.tscanAutoFile()=false 兜底。
  2. 写回归:把该攻击作为回归用例写进 src/agent/eval/tasks.ts,加 1 条断言(攻击应被挡)。
  3. 重放验证:重放攻击,确认已被拦截。
  4. 跑 gatepnpm eval:gatesrc/agent/eval/gate.ts fail-closed + scripts/eval-gate.ts)确认转绿——回归断言通过且无指标退化。
  5. 基线待办agent-evals/baseline.json 尚未选定基线 run,gate 的 completionRate 退化检查需选定基线后才能完整生效。

4. 今日实测 / 产出

  • toolRegistry.ts + hitl.ts + eval gate(gate.ts / scripts/eval-gate.ts已建(✅)
  • 攻击重放被拦 + 回归断言转绿待跑(需 key 跑出真攻击后实现修复)
  • 目标产出:重放被拦 + 回归测试绿。
  • agent-evals/baseline.json 尚未选定基线 run。

诚实状态:修复着力点(toolRegistry/hitl/gate)是 ✅ 已建的确定性代码;攻击重放被拦与回归转绿仍「待跑(需 key)」——必须先有真攻击 transcript 才能实现并验证修复,不升级状态。

5. 常见误区 / 陷阱

  • 抓到不写回归:只「这次挡住」不写自动化断言,换模型/重构时洞会悄悄复活。必须把真攻击钉成 tasks.ts 的断言。
  • fail-open gate:让 gate「出错/缺数据就放行」是安全反模式。gate.ts 的纪律是 fail-closed——缺指标/NaN 即红灯。
  • 靠改模型修安全:模型行为不可完全控制,安全必须靠模型外的确定性护栏(toolRegistry 拦截、HITL gate)兜底,而非寄望「下次模型不会被骗」。
  • 没选基线就以为 gate 全生效agent-evals/baseline.json 未选定时,completionRate 退化检查无基线可比——须先选定基线 run 才能完整守护退化。

6. 学习资源(每条带 YYYY-MM)

  • OWASP Top 10 for LLM Applications(2025)— LLM02/LLM06 防御与修复指引
  • OCC 2026-13 — 模型风险治理(2026,SR 11-7 于 2026-04-17 撤销后接替)
  • FinCEN SAR 归档规则(BSA)— SAR 人审/归档法定要求,HITL gate 依据
  • Anthropic «Demystifying evals»(2026-01)— eval gate 与持续评测纪律

7. 修复闭环全景图(B9 收口)

[Day 88 攻击集] → [Day 89 ≥1 真攻击确认]
                          │
          ┌───────────────┴───────────────┐
          ▼ 事中拦截                        ▼ 强制人审
   toolRegistry.ts                     hitl.ts
   ├ validate(inputSchema)             ├ canAutoFile(): false(字面量)
   ├ 越权工具调用拒绝                  ├ file() 仅 approved 态
   └ 输出过滤(拦泄露/不安全输出)     └ append-only 审计轨迹
          │                                │
          └───────────────┬───────────────┘
                          ▼  写回归断言进 tasks.ts
                  [重放攻击 → 被拦]
                          ▼  pnpm eval:gate(fail-closed)
                  [回归测试绿 / 无指标退化]
                          ▲
                  baseline.json(尚未选定基线 run)

两层防御(事中拦截 + 强制人审)+ 回归断言 + fail-closed gate,把「抓到的洞」钉成「永久守着的绿线」——这是红队闭环的终点,也是 B9 的收口。

8. 面试自测(30 秒口径)

  • 红队闭环为什么必须写回归? 只修不写回归,换模型/重构时洞会悄悄复活;回归断言让机器永久守着。
  • fail-closed 是什么? 配了阈值但当前指标缺失/NaN → 红灯,绝不静默放行;与「出错就放行」的 fail-open 相反。
  • 为什么不靠改模型修安全? 模型行为不可完全控制,安全要靠模型外的确定性护栏(toolRegistry/HITL)兜底。
  • SAR 路径的硬护栏是什么? canAutoFile() 返回字面量 false + 状态机 file() 仅 approved 态——诱导自动归档撞硬墙。
  • baseline 没选会怎样? completionRate 退化检查无基线可比(gate.ts:36 守卫不触发),须先选定 baseline.json

SOTA检查 (2026-06 更新)

  • 主流方案:eval gate fail-closed + HITL 人审为合规主线;事中拦截(toolRegistry 策略)+ 回归用例固化为红队闭环标准,仍 SOTA。
  • 须复验:治理引用须用 OCC 2026-13勿再引已撤销的 SR 11-7,2026-04-17 撤销);EU AI Act 高风险义务推迟至 2027-12-02、Art.50 透明度 2026-08-02 生效——日期勿用旧时间线
  • 过时黑名单:避免 fail-open gate;避免「AI Act 高风险 2026-08 生效」的旧时间线;避免引已撤销的 SR 11-7。
  • 下次复查点:2026-08-02 Art.50 生效前复核透明度义务;OCC 2026-13 后续指引执行当周跟进;agent-evals/baseline.json 选定基线后接入 gate。

衔接

  • 昨天:Day 89 — 攻击评级 + 真攻击确认(独立评级确认 ≥1 条真攻击)
  • 今天:对真攻击落 toolRegistry 策略/输出过滤 + HITL 兜底,写回归断言进 tasks.ts,跑 fail-closed gate 转绿——B9 红队闭环收口
  • 明天:Day 91 — 延迟/吞吐指标体系(M4,TTFT/TPOT + p95/p99),B10 转向 p95/cost 测量 + 成本级联 + tau2-bench