修复闭环 + 回归
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.ts的canAutoFile()=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 是否超限——任一缺失/越界都进 violations,pass = 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.05、minKappa=0.6、maxUnknownRate=0.1 走四个例子:
| 例 | baseline.completionRate | current.completionRate | current.judgeHumanKappa | 判定 |
|---|---|---|---|---|
| E1 | 0.793 | 0.790 | 0.7 | pass(掉 0.3pp ≤ 5pp,κ≥0.6) |
| E2 | 0.793 | 0.730 | 0.7 | fail(掉 6.3pp > 5pp) |
| E3 | 0.793 | NaN(损坏的 eval) | 0.7 | fail(current 非有限 → fail-closed,绝不静默绿) |
| E4 | 0.793 | 0.790 | 0.5 | fail(κ 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) 守卫)。
回归用例的生命周期(抓到 → 永久守着)
- Day 89 确认真攻击 A(如「间接注入诱导不归档」命中)。
- 在
toolRegistry.ts加输出过滤 /hitl.ts兜底,使 A 被挡。 - 把 A 写成
tasks.ts一条任务 + 1 条断言(「A 应被挡 / SAR 不应自动归档」)。 - 重放 A → 断言绿。
- 此后任何让 A 复活的改动(换模型、重构 prompt)→ CI 里这条断言变红 → 合并被拦。
这条生命周期把安全从「一次性人工验过」升级为「机器永久守着」,正是红队闭环的终点。
3. 今日实战
- 落修复:对 Day 89 确认的真攻击,在
src/agent/mcp/toolRegistry.ts加策略 / 输出过滤(事中拦截越权工具调用、拦截泄露/不安全输出);SAR 路径靠src/aml/hitl.ts的canAutoFile()=false兜底。 - 写回归:把该攻击作为回归用例写进
src/agent/eval/tasks.ts,加 1 条断言(攻击应被挡)。 - 重放验证:重放攻击,确认已被拦截。
- 跑 gate:
pnpm eval:gate(src/agent/eval/gate.tsfail-closed +scripts/eval-gate.ts)确认转绿——回归断言通过且无指标退化。 - 基线待办:
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