日期:2026-07-24
本文为工程实践复盘,客户信息已脱敏;所述数据均为示意值,不对应任何具体合同或审计数字。
我们当时给一家制造企业的内部知识问答上线大模型,灰度期间有位同事闲来无事"逗"模型,结果把系统提示词的片段套了出来,还顺着提示让模型尝试调用一个本不该对他开放的查询工具。这件事给我们敲了警钟:功能测试过了不代表安全,越狱、提示注入、未授权工具调用这些风险,靠线上暴露来发现代价太大。更麻烦的是,模型上线前我们其实做过一轮"人工试毒",但三个人各试各的,没有用例沉淀、没有覆盖度量,结论全凭感觉,根本说不清到底测了什么、漏了什么。我们决定建立一套安全红队评测与对抗测试机制——用"攻击者"视角持续探测护栏的薄弱点,按风险分级、形成修复闭环,而不是等问题上了生产再救火。底线是:整个过程在私有环境闭环,评测用例脱敏,不沉淀任何可被直接复用的攻击脚本。
我们把红队能力拆成三层。第一层是"对抗用例库":按风险类型沉淀用例(提示注入、角色扮演越狱、系统提示泄露探测、未授权工具调用、敏感/违规内容生成),每条用例只保留"探测意图"和"期望拦截行为",不包含可直接复用的攻击载荷,且不绑定任何具体业务机密。第二层是"自动化越狱探测":用一个受控的探测模型批量生成变体问法,喂给被测模型加护栏组合,以"是否绕过护栏"作为判定,而不是看模型是否真的输出了有害内容——这样探针本身不产出有害样本。第三层是"风险分级与修复闭环":命中的绕过按影响面分 P0/P1/P2,P0 直接阻断上线,P1 限期修复,每条发现都转成护栏规则或对齐样本,并在下一轮红队里做回归复测,确认闭环。护栏的输入侧与输出侧屏幕,本就是我们采用的 AI 网关的标准能力,红队只是持续考验它们,把"我以为安全"变成"我测过安全"。
第一个难点是探测模型不能真的产出有害内容。我们的做法是:探测模型只负责"构造绕过话术",被测模型加护栏组合才是真正的执行对象;判定标准是被测链路是否放行了违规请求,而不是内容本身有多危险,从源头避免红队脚本变成攻击教材。第二个难点是系统提示泄露。我们给护栏加了"提示词指纹"识别:凡是试图让模型复述 system prompt、暴露内部工具 schema 或权限说明的请求,统一在输入侧拦截并兜底回复,不让模型进入"解释自己"的语境。第三个难点是工具调用鉴权。红队最容易钻的空子是"让模型以为用户在授权",我们改为在网关层做工具调用鉴权——每次工具调用都校验调用主体、工具白名单和当前会话角色,模型自身的"同意"不再算数,越权请求在网关直接拒掉。第四个难点是风险分级的一致性:我们用"影响面×可利用性"二维打分,明确 P0 是能触达真实数据或真实工具的高危绕过,P1 是可越权读取但无写执行,P2 仅体验类绕过,避免人力被低危噪点淹没。
案例片段(已脱敏):工具调用鉴权在网关侧的简化校验(伪代码):
python def allow_tool(user, tool, session): if tool not in session.role.tools_allowlist: # 角色白名单 return False if not acl_check(user, tool): # 主体 ACL return False if session.risk_level >= 2: # 高风险会话降级 return False return True
以脱敏示意口径看:红队首轮探测时,高危(P0)绕过发现约 11 处,主要集中在工具调用鉴权与提示泄露两类;经过一轮修复后再测,P0 绕过归零,P1 类绕过由约 23 处降至 4 处。对抗用例库覆盖的风险类型由最初的 4 类扩展到 8 类,自动化探测的变体生成覆盖率提升约 3 倍,单轮探测耗时从人工约 2 天压缩到自动约 40 分钟。上线后一个季度内,线上因越狱导致的违规输出工单下降约 76%。修复闭环的平均耗时由最初人工跟进了约 5 天,压缩到约 1.5 天,且每次修复都自动进入回归集,避免"修了又犯"。所有数值均为示意值,不对具体合同负责。
安全要先攻后守,红队常态化比一次性渗透更有价值——攻击手法一直在变,护栏也要持续被考验,靠一次人工试毒远远不够。对抗用例只存"探测意图"与"期望拦截行为",绝不沉淀可直接复用的攻击载荷,这是合规底线,也是探针不产有害样本的保证。系统提示泄露要在输入侧前置拦截,别让模型进入"解释自己"的语境。工具调用鉴权必须下沉到网关层,模型说"可以"不算数,主体 ACL 加角色白名单才是依据。风险分级用"影响面×可利用性"打分,把人力留给 P0,别被低危噪点拖垮;每轮修复都进回归集,安全能力才能滚雪球。
案例片段(已脱敏):红队风险分级打分片段:
yaml risk_grading: P0: 触达真实数据或真实工具的高危绕过 P1: 可越权读取但无写/执行 P2: 仅体验类绕过(如角色扮演) gate: { block_on: [P0], fix_within_days: { P1: 3, P2: 7 } }