跳到主要内容

那个保护了日志却让模型泄露输出结果的 PII 脱敏器

· 阅读需 13 分钟
Tian Pan
Software Engineer

一个仅针对入站流量运行的 PII 脱敏器就像是安装在管道错误一端的单向阀。它在用户提交的姓名、电子邮件和账号进入日志之前拦截它们。但它对模型的输出无能为力 —— 而现在,模型正是在输出端积极地组合可能包含这些相同标识符的文本,这些内容可能源自 RAG 检索、工具返回、对话历史或用户从另一个租户数据中粘贴的内容。我观察过每一个上线了输入端脱敏器的团队,他们的待办事项中都有一个标记为“输出端对齐”的后续任务。大多数这类任务永远不会关闭,因为在长达六个月的时间里,没有任何事故暴露出这个缺口。六个月后,该任务经过多次重新排序,看起来更像是一个功能需求,而不是缺失的一半安全控制手段。

失败模式是恒定的:输入端脱敏被视为标准的控制手段,因为它的工程问题更简单,审计故事也更容易讲。你编写了一套正则表达式,运行了一个标注好的基准测试,证明了在固定语料库上的精确率和召回率,在特性开关后上线了它,安全评审也将其接受为 PII 边界。输出端则完全没有这些优势。模型的响应是生成性的,表面积是无限的,而且测试方法论 —— “在无限多的上下文中它不应该说什么” —— 在结构上比“我们应该从已知输入中剥离什么”要困难得多。因此,上线入口端的团队将出口端视为未来的工作,而这个未来永远不会到来,直到有客户举报另一个客户的电子邮件出现在他们的对话记录中。

当模型成为“写作者”后,脱敏器的信任假设就不复存在了

最初支持仅在输入端脱敏的论点是架构层面的。当时的逻辑是,模型是一个无状态函数:它无法泄露它从未见过的内容。只要从提示词(prompt)中剥离 PII,响应中就不会包含它。在“前智能体”时代,这大致是正确的,当时大语言模型(LLM)只是一个单次调用的摘要器或分类器,用户的消息是唯一有意义的输入。脱敏器位于 API 网关,清洗入站载荷,架构是对称的:干净的输入,干净的输出。

当智能体开始从 RAG 语料库读取数据、调用工具并持久化对话状态时,这种对称性就被打破了。一个从向量数据库中检索片段的模型,正在读取输入脱敏器从未见过的内容。一个调用客户查询工具的模型,正被喂入来自脱敏器从未触及的系统的电子邮件地址。一个维护多轮对话记录的模型,正在基于一段历史记录撰写当前的回复,而该记录中最早的项目可能早于脱敏器的部署。模型不再只是当前提示词的函数;它是流入请求的每一个字节上下文的函数,而这些字节中的大多数从未经过脱敏器保护的入口。

最重要的路径正是架构上显式信任的那条路径。支持人员需要客户的电子邮件来查询账户。编码助手需要用户的代码,其中可能包含脱敏器本该剥离的标识符。文档问答机器人需要引用包含姓名的文档。非脱敏路径并非疏忽 —— 它正是系统的价值主张。一旦你的价值主张要求模型看到真实的 PII,模型就可以将这些 PII 写回它生成的任何输出中,呈现在它服务的任何其他对话中,而入口处的脱敏器对此一无所知。

跨租户内容披着当前租户的外衣抵达

最清晰的失败模式是用户将另一个客户的电子邮件粘贴到自己的对话中。入口处的脱敏器看到的是看似租户内的内容 —— 用户已通过身份验证,请求位于他们自己的会话中,载荷是他们自己的消息 —— 因此要么不进行脱敏(因为路径是可信的),要么进行脱敏并重新插入一个令牌(token),而智能体在输出时会忠实地将其解码回原始值。模型对粘贴的线程进行摘要,摘要中逐字包含了另一个客户的电子邮件,用户发现了这一点,于是你的平台是否符合 GDPR(通用数据保护条例)的问题就变成了董事会级别的对话,而不再仅仅是工程问题。

荷兰数据保护局在 2024 年收到了一个完全属于此类性质的通报:一名电信员工将包含客户地址的文件粘贴到了 AI 聊天机器人中,将数据输入聊天机器人的行为被视为 GDPR 下的未经授权披露。72 小时的通知计时从控制者意识到泄露的那一刻开始,而意识的标准不是安全团队的意识,而是组织的意识 —— 受影响用户提交的支持工单很容易触发这一点。罚款上限是 2000 万欧元或全球年度总营业额的 4%(以较高者为准),2024 年的 GDPR 罚款总计超过了 12 亿欧元。此类事件不一定非要是恶意的才算违规。它不一定非要在技术规模上很大。它只需要涉及个人数据跨越了数据处理者本应执行的边界。

架构在这里的失败在于,脱敏器是针对“用户即攻击者”的威胁模型设计的,即防止用户尝试将自己的 PII 存入你的日志。在多租户智能体系统中,真正的威胁模型是“用户即数据骡子”,他们将另一个租户的 PII 带入模型的上下文中,并要求模型执行常规操作。脱敏器看错了方向。

输出脱敏不仅仅是输入脱敏的缩小版

最省事的做法是直接把输入脱敏器套在输出路径上,然后大功告成。但这比听起来要难得多,其中的差异正是为什么大多数团队将输出脱敏视为一个独立的、推迟的项目,而不是一个简单的开关切换。

输入脱敏运行在已知的模式(Schema)上。用户消息具有结构,脱敏器可以对其进行 Token 化,运行命名实体识别(NER),通过正则表达式匹配电子邮件和电话号码,并生成具有高召回率的标记跨度列表。而输出则是非结构化的自然语言,其内容分布取决于模型的决策。同一个字符串 [email protected] 在输出中可能会以 [email protected] 出现,也可能表现为 john at example dot com,或者 the email johnatexampledotcom,甚至是一种转述,如 the customer's contact email starts with john and uses the example domain —— 其中每一种形式都能规避针对规范格式调校的正则表达式。微软的 Presidio 框架是目前应用最广泛的此类工作开源工具包,即便是在 Presidio 的文档中,也区分了输入端的 output_parse_pii 模式(在模型调用后对 Token 进行反掩码)和输出端的 presidio_filter_scope: output 模式(主动扫描模型的响应)。它们是不同的代码路径,因为它们是不同的问题。

延迟是第二个区别。输入脱敏只需运行一次,其请求大小受限于用户的打字速度。而输出脱敏必须在流式响应的每个块(Chunk)上运行,其预算要对照模型的每秒 Token 数(Tokens-per-second)来衡量,否则它就必须缓冲整个响应并延迟首屏渲染,直到扫描完成。那些试图在一个下午将入口脱敏器强行安装到出口的团队通常会发现,用户感知的延迟翻倍了,流式体验(UX)坏了,部署在第二天早上就被回滚了。输出端脱敏器是一个真正的基础设施,而不仅仅是一个配置更改。

第三个区别是语义鸿沟。输入脱敏可以非常严格,因为误报的代价仅仅是用户重新输入查询。输出脱敏则不能严格,因为误报的代价是模型的响应失去了你试图检索的信息。一个支持代理在回复关于客户账户的信息时,由于“热心”地脱敏了该客户自己的电子邮件,这比发生泄露的产品更糟糕。修复方法不是“脱敏更多”;修复方法是搞清楚你针对的是谁的 PII 进行脱敏。这需要一个内容溯源(Content-provenance)信号 —— 即输出中每个跨度(Span)上的一个标签,用以说明模型是从哪个租户的数据中提取的 —— 而这种信号在任何现成的脱敏库中都不存在。

溯源才是脱敏本应代理的控制权

弥合这一鸿沟的架构认知是:PII 不是一种内容类别;它是一种关系类别。当一个电子邮件属于你不拥有的客户时,它是敏感的。而当同一个电子邮件属于粘贴它的用户时,将其展示出来是没问题的。脱敏器的工作从来都不是寻找电子邮件;它的职责是在属于对话所有者的数据与不属于它的数据之间强制执行边界。只要进入系统的唯一 PII 是用户自己的,输入脱敏就是该边界的一个有用代理。一旦输入包含了 RAG 检索结果、工具返回和粘贴的跨租户内容,这个代理就失效了,因为数据的来源不再等同于数据的身份。

内容溯源才是脱敏器原本试图模拟的控制权。进入模型上下文的每个块 —— 无论是来自工具、检索、转录还是用户粘贴 —— 都应该带有一个标签,说明其作者是哪个租户。然后可以扫描模型的响应,寻找与已标记块匹配的跨度,输出脱敏策略可以针对每个跨度决定是允许、遮蔽还是隔离。与当前对话所有者创作的块相匹配的跨度是被允许的。与不同租户创作的块相匹配的跨度则被遮蔽或拒绝。与任何标记块都不匹配的跨度则作为回退方案,由通用的 PII 检测器进行审查。这与脱敏器试图实现的控制权相同,不同之处在于它正确地聚焦在了真正起作用的边界上。

工具白名单(Tool-allowlisting)是将相同理念应用于出站调用。一个作用域为“任何 HTTPS 端点”的代理 URL 获取工具是一个通用的数据流出通道,代理会非常乐意利用它将上下文外泄到模型被告知发送的任何地方。将工具的作用域限制在已知供应商域名的每租户白名单内,可以将相同的工具转变为每租户的控制平面。代价是微小的灵活性损失;好处是模型的 URL 获取指令不再是一个自由形式的外泄原语。

对话记录层(Conversation-transcript layer)是溯源必须落地的第三个地方。记录是一个持久的系统,将包含另一个租户 PII 的代理消息持久化到错误租户的记录中,这种行为将运行时泄露转化为了存储性违规。在记录写入层执行的一项策略 —— 拒绝持久化任何包含未标记为属于对话所有者的 PII 的消息 —— 即使在脱敏器遗漏且模型生成了该内容的情况下,也能拦截泄露。

本季度行动建议

三种模式可以在不重新架构平台的情况下弥合差距。第一,每周运行一次对抗性输出脱敏测试:向智能体(agent)中粘贴一条包含跨租户 PII(你的团队自己的电子邮件即可作为“金丝雀”数据)的消息,向智能体提出一个需要总结该消息的问题,并断言响应中没有回显该地址。这是你能构建的最廉价的检测器,它能捕获导致荷兰电信案例崩溃的那类回归缺陷。第二,增加输出侧扫描,使用 Presidio 等工具或供应商提供的等效工具,即使最初它只能在非流式模式下运行;尽管增加 200 毫秒延迟的输出扫描比流式响应的 UX 稍差,但它能提供更好的防泄露态势。第三,在摄入时为每个检索到的块(chunk)和工具返回结果标记租户标签,将标签与内容一起存储,并将其传播到转录层编写的任何审计追踪中;这是未来基于来源(provenance-based)的输出过滤所需的基础,且早期实施成本很低。

更难的问题在于契约层面。入口处的脱敏器是一种发布后即可高枕无忧的控制措施。而输出侧的隐私态势则是一项持续的工程承诺,因为模型的响应分布会随着每一次提示词更改、每一次检索更改以及每一次模型升级而发生偏移。如果一个团队将输出隐私视为一次性任务,那么当模型围绕本应被正则表达式捕获的电子邮件进行改写时,他们就得在 72 小时内编写违规通知了。脱敏器本应执行的边界是属于当前对话的数据与不属于当前对话的数据之间的边界,而模型正是最擅长越过这一边界的参与者。如果你的隐私控制只盯着用户输入的那一侧,就等同于让模型输出的那一侧门户大开,而模型才是产出内容最多的一方。

References:Let's stay in touch and Follow me for more thoughts and updates