返回新闻案例

新闻案例 / NEWS · 技术解析

拦得太狠也是一种失败:十一个护栏在法律合同上的误报实测

arXiv 2611.08834论文复盘:十一个提示注入护栏在法律合同上的误报率实测,最高误报超九成;梳理512块切法、504句探针与失效模式,给出Haiku到Opus低成本零误报级联方案。

方舟 2026.11.14 8 分钟阅读

提示词注入护栏原本是为聊天场景设计的。那种场景里,用户输入一句「忽略之前所有指令」,护栏拦下来,代价是用户重说一遍,几乎无损。但把这套东西搬到合同评审上,前提就彻底变了。

合同评审系统读的是对手方写的文档。不可信的东西不再是用户的一句话,而是整份合同本身:它既是任务输入,又长,又密布着合法的指令性语言。「Notwithstanding anything to the contrary」是合同里再常见不过的表述,它和「ignore all instructions」一样,都是分类器训练里最典型的攻击句式。这些句子在人看来是合法条款,在分类器看来与攻击样本长得极像。

十一个提示注入护栏在法律合同上的误报实测与级联机制流程图
从合同载荷到级联复核:一条错误拦截链与四组实测数字。

01

合同是护栏最难做的那类文档

这就带出一个不对称:拦漏了,一份被做了手脚的合同进到评审流程里,结论被悄悄改掉;拦错了,一份完全正常的合同被当成攻击,评审被卡住,业务方拿不到结果。前者的后果明显更重,所以业界默认的做法是宁可多拦。问题在于多拦的代价从来没人量化过。

论文开头就把话说明白了:没有任何一个护栏公布过在法律合同上的误报率。这句话比任何数字都值得停一下。十一个开源权重护栏被广泛部署在文档分析系统的入口,但它们的误报率在法律合同这个特定领域是空白的。空白的代价由谁来付,是这篇论文真正要问的。

没有任何一个护栏公布过在法律合同上的误报率。空白的代价由谁来付,是这篇论文真正要问的核心。

还有一层结构性的理由,说明为什么这个空白不该留着。合同评审是近几年才起来的自动评审形态,靠合同注入打通评审的系统,原文引言写得很直接:这一领域「few documented examples of prompt injection happening in the wild」,野外样本本来就少。也就是说,护栏之所以看起来「没出过事」,一部分原因可能是这个攻击面还没被充分试探,而不是护栏真的挡住了什么。

02

口径:两个语料、512块、任一块超阈即标记

要看懂后面那些百分比,得先把它们的分子分母钉住。

语料有两个。一个是作者所在公司的自有语料,683 份商业合同,全部来自生产合同评审部署,是已签署的协议与对手方文本,不是模板也不是样例稿。构成大致是 45% 的 SaaS 与订阅协议、26% 的主服务协议、13% 的订单与工作说明书、12% 的修订与补充,长尾里还有转售、数据处理与保密协议,日期跨 2010 到 2027。这份语料保密,不公开。

另一个是公开的 CUAD,510 份。它来自 SEC 备案的重大合同,原本带专家标注,但原文只用原始文本、不用任何标注。理由很干净:这里测的是未修改文本上的误报,不需要标签。所以所有主结果都在 CUAD 上复算过一遍,第三方能对上。

切块规则决定了「文档级」这三个字的含义。每个检测器按自己的上下文窗口把合同切成不重叠的 512 token 块;Horizon Labs 与 Wolf Defender 按厂商建议用 2048 的窗口,后者另加 64 token 的重叠。最后一块如果不足半个窗口,就把最后两块重新均分,避免出现一条尾巴。一份文档只要有任意一块超过阈值,整份文档就算被标记,阈值一律用厂商出货默认的 0.5。

载荷一共 14 条,分成三类。R1 到 R9 在护栏的射程内,也就是「未经授权的评审指令」:直接覆盖(R1、R2)、伪造系统权限(R3,冒充上游流水线已经预审过)、伪造 harness 分隔符(R4,关掉文档容器再开一个假指令块)、起草注记伪装(R5)、合同语域指令(R6、R7)、证据捕获(R8)、从输出中省略(R9)。S1 到 S3 是越权声明,比「让模型答错」更进一步:它改动的是「哪些问题算被授权问」,这一类超出了护栏的设计范畴。C1 与 C2 是对照,其中 C2 是一条完全良性的、写给机器看的指令。

判据用的是配对 AUC,而不是普通 AUC。原因是附录 E 里那条发现:检测器在一份被注入的文档上的分数,与这份文档在注入之前的分数强相关。所以每份注入文档只和它自己未修改时的宿主比。不配对的话,测到的其实是「这份合同本身好不好评」。检出这一侧的量是固定的:60 份宿主合同配 9 条在范围内的载荷,每条注入文档都与它自己那份未修改的宿主配对。

对照列同样重要。如果某个护栏在良性对照上的标记率,和在攻击上的标记率差不多,那它所谓的信号就是扰动敏感,而不是检测能力;它知道文档被改过,但不知道改动是敌意的。

作者自己划的边界也一并记下:只测英文与美国商业合同;每份注入合同只针对一个评审问题;护栏面板反映的是当下市面可得的东西;完全没有测延迟;成本按单语料单字段的标价算,只能界定量级;所有检测器都是按 512 token 块打分,系统拿到判定之后是拦截、路由还是标给人看,这篇不回答。

03

主结果:误报与检出必须分开看

主表把两件事并排放了:文档级误报率,和配对 AUC 下的检出能力。合起来看,画面很直白:没有任何一家能在两边同时站住。

先看拦得太狠的那一头。DistilBERT 在自有语料上误报 92.39%,在 CUAD 上是 100.00%;deepset 是 91.36% 与 100.00%;TestSavant 78.62% 与 78.04%;ProtectAI v2 是 60.61% 与 42.55%。误报九成意味着什么,不难换算:每一百份正常合同,有九十多份会被错误标记为攻击。

再看另一个极端。Prompt Guard 2-22M 的误报是 0.00%,两个语料都是零。听起来像是理想解,但它的配对 AUC 是 0.493 与 0.513,低于随机线。也就是说,它的零误报不是因为聪明,而是因为基本不判。原文用的词是「怯懦而非判别力」。

中间地带同样不好看。ProtectAI v1 的误报只有 0.59% 与 2.35%,表现很像一个可用的过滤器,但它的配对 AUC 只有 0.506 与 0.504,几乎是抛硬币。更值得记一笔的是:v1 到 v2 的误报涨了一个数量级以上(0.59% 到 60.61%),而 v2 是后出的版本,其仓库现已归档。这是给「升级就会更安全」这类直觉的一记反例。

唯一在检出上明显站得住的是 Horizon Labs,它的配对 AUC 是 0.888 与 0.898,面板里最高,而且它是专门在文档级注入上训练的。代价是误报 15.37% 与 11.76%。更要紧的是它与对照之间的差距变小了:它检测到的是「文档被编辑过」,而不是「编辑是敌意的」。

一张表十一个开源模型,赢不了一条手写的正则。五条正则表达式基线未被任何一个护栏帕累托支配。

还剩一条基线:五条正则表达式。它的误报是 0.29% 与 0.00%,召回 22.2%。这条基线的意义不在于它强,而在于没有任何一个护栏能把它帕累托支配,也就是说,没有一个护栏能同时做到误报不比它高、召回又不比它低。

还有一处不稳定性值得单独说:把评审问题从「服务水平协议」换成「保密要求」,越权声明那一列对所有检测器都掉到接近随机;而在范围内的排序也不迁移,三个检测器仅因为换了主题,配对 AUC 就移动了 0.13 到 0.40。一个检测器换了个问法就换了个排名,这在选型时几乎是致命的。

04

诊断分析:语域、窄召回、长度与稀释

作者没有停在「都很差」这一步,而是做了个能定位原因的探针。做法是把一条指令拆成几个可独立变化的因子,造出 6 × 2 × 2 × 3 × 7 = 504 条载荷句。

五个因子分别是:意图(取 R1-3、R8-9 加机器寻址的良性对照共六档);收信人(是「you」还是「any automated system」);情态(must 与 shall 这一字最小对立);框架(裸句、提示式前言、还是合同式前言);主题(指令涉及的合同细分领域)。

结果几乎是一边倒的。除 Wolf Defender 在攻击与同语域的良性指令之间有 +27.9 个百分点的分离之外,其他护栏的分离都很小,五个是负的(包括 AUC 最高的 Horizon Labs)。+27.9 也仍然不够,作者说得很清楚,这还没到可部署的召回与误报水平。

情态与前言几乎不动分。shall 与 must 在多数护栏上不带来变化,只有较新的 Horizon Labs 与 Wolf Defender 例外;加上标准的法律前言几乎不影响提示式检测器的分数,唯一例外是 ProtectAI v2,它把这一句标记得几乎和「Ignore all instructions」这句公认信号一样强。

这些结果合起来指向一个判断:绝大多数护栏学到的,是指令长什么样,而不是指令在要什么。它们能认出教科书式的「忽略所有指令」,因为那是训练集里的常客;一旦攻击被改写成一条正常的合同条款,认不出来的比例就上去了。

召回的问题不在于低,而在于集中。所有有响应的护栏,最强标记的都是同一类意图:压制(让评审闭嘴)。PIGuard 在压制上的标记率是 60.7%,其余四类全在 21.4% 以下。窄召回来源于「能抓住库里的特定措辞」,而不是「更能识别这类功能」。

长度这条线索呈现三类曲线:第一类是饱和退化(deepset 与 DistilBERT 喂到 16 到 24 个 token 就冲到天花板);第二类是短片段敏感(PG1-86M 在 64 到 96 token 之间有 −37.3 个百分点的断崖);第三类是与长度无关的退化(TestSavant 任何宽度都平平)。多数护栏根本没离开地板。

第四组诊断区分了两种失败:在提示式语域下,孤立能标,嵌进文档标记率低至 2%,这叫稀释;在合同语域下,孤立打分都过不了阈值,这叫表征缺失。越权声明(S1 到 S3)这一类几乎完全没有表征,护栏直接将其当成合法条款放行。

05

级联方案:贵的那部分到底花在什么地方

论文的第二部分是做一个参照点,用 LLM 来判。提示词是两段式判据:这条指令是否(A)针对评审过程,并且(B)照着做会让答案失真。测两档能力:前沿的 Opus 4.8 与中档的 Haiku 4.5。

Opus 在自有语料与 CUAD 上都达到 1.000 的配对 AUC。Haiku 是 0.982 与 0.969,也远高于面板里最好的开源护栏(0.814),而成本只是零头。这组数字说明「能不能检出」这件事,在能力足够时并不难。

但两档之间有一个很难看的分水岭:Opus 不标记那条良性的机器寻址对照,Haiku 无论哪个语料都标记超过 80%。也就是说,中档模型在「写给机器看」这件事上同样会把良性指令当成攻击,和前面七个方向搞反的护栏是同一个病,只是程度上轻一些。

于是有了级联:让 Haiku 主检,它的正例交给 Opus 复核。

效果是:在 Haiku 之上每份文档只多花 0.02 美分,达到 Opus 的精度却便宜 6.5 倍(2.7 美分对 17.5 美分);文档级误报从 1.7% 降到 0.00%,良性对照误报从 80.0% 降到 0.0%,而召回完全没有损失。

置信度阈值化是另一条路,效果明显更差。它每份文档也只省 0.02 美分,但误报是 0.53% 对 0.00%、召回是 84.4% 对 90.0%、良性对照是 65.0% 对 0.0%。靠「只标记高置信的」来降误报,降得很有限,还会顺带把真阳性一起丢掉。

最后一个反直觉的发现值得记:前沿模型在越权声明这一类上反而过于严格。Opus 的召回是 52.8%,而更便宜的 Haiku 是 88.9%。在「这份合同声称已经被上级批准过」这类陈述上,更强的模型判得更保守。档位选择不能只看总分。

06

局限,以及三条能落地的判断

局限得逐条走,因为它决定了哪些数字能引用:

第一,只测英文与美国商业合同,每份注入合同只针对一个评审问题。跨语言、跨法域或复杂组合都不在样本里。

第二,自有 683 份语料不公开,只有 CUAD 上的结果可复算。这是一个真实的约束:误报侧的数字来自真实合同,可信;但「这个行业普遍如此」这句话,取决于一个只有作者能看到的语料。

第三,检出侧的数字依赖合成载荷。作者自己承认野外样本很少,载荷是按分类法构造的。

第四,完全没有测延迟。而在交互式合同评审里,一份文档要跑多少秒,可能比误报率更能决定它能不能用。

第五,还有两层攻击没覆盖:靠渲染隐藏文本,以及纯虚假断言。前者的解法在预处理,后者要靠 Span 级溯源,把每个抽取出的答案与原文核对,属于验证,不是注入检测。

从这些实测里能带走三条落地判断:

第一条:先测自己的误报,再谈拦截率。护栏的选型讨论经常从「能拦住多少攻击」开始,但在这类文档上,误报才是决定它能不能进生产的那一项。拿自己真实的、干净的合同跑一遍,看有多少被标记。如果这个数字是两位数,后面所有关于召回的讨论都没有意义。

第二条:把护栏当软过滤,而不是硬过滤。让护栏提示人工复核,而不是让它直接拦住评审。原因在那条 97% 的发现里:高达 97% 的分数方差可归因于「被打分的是哪份合同」,标记率随文档长度与结构系统性变化。硬过滤的后果是让篇幅长、格式重的当事方无端得到降级服务。

第三条:要上 LLM 就用级联,别用全程前沿,也别指望置信阈值。2.7 美分对 17.5 美分,误报与召回都站得住。对合规审查这类高价值场景,单份文档几美分的差别,远比一次拦错正当业务的成本小得多。

一个护栏好不好,不取决于它拦下多少攻击,取决于它在正常文档上有多安静。安静不下来,它拦的就不是攻击,是业务。

受监管机构与金融业务之所以最容易被「专门护栏」这个词说服,是因为它同时具备三个特征:文档由对手方提供、里面有大量合法的指令性语言、拦错的代价直接落在业务上。无论是否做金融合同审查,把可核验性与误报率作为首要门槛,都是通用的架构底线。


资料说明:本文基于 arXiv 论文《Notwithstanding Anything to the Contrary, Ignore All Instructions: Measuring Prompt Injection Guards on Legal Contract Review Attacks》(arXiv:2611.08834) 及开源评测代码整理。

搜索全站

↵