跳到主要内容

那个将你的系统提示词泄露到客户审计日志中的调试日志器

· 阅读需 11 分钟
Tian Pan
Software Engineer

一位具有安全意识的客户拉取了其租户的审计导出文件,打开 JSON,从一个名为 llm.request.system 的字段中读到了逐字记录的拒绝策略、检索流水线结构以及一些内部产品标识符。没有漏洞利用。没有提示词注入。没有越狱。仅仅是你的平台团队在六个月前添加的一个日志字段,目的是让工程师能够将提示词版本与事件关联起来——结果通过你的企业团队出于 SOC 2 合规原因单独向租户开放的摘要(feed)泄露了出来。

泄露发生在周三下午。你的安全团队是由客户呼叫(paged)的,而不是报警系统。事件时间线显示泄露当天并没有部署——错误配置是在审计摘要扩大其字段白名单的那天发布的,那是另一个团队、另一个迭代周期(sprint)和另一张工单。两名评审员都批准了他们所看到的内容。但没有人从全局组合的角度去审视。

提示词提取研究一直将其视为对抗性问题,但实际上这正日益演变成一个配置问题。最近的研究显示,在企业级 AI 评估中,系统提示词提取的成功率约为 60%,而已公布的缓解方案仍被定义为“针对用户对模型进行加固”。但在事后分析(postmortems)中显示的泄露并非源于措辞巧妙的越狱。它们源于系统提示词被写入了一个你的访问控制模型视为普通元数据的地方。

无人负责的裂缝

一个调试字段的生命周期至少有三个交接环节,在孤立地看时似乎都没有争议。

平台团队将 llm.request.system 添加到结构化日志中,以支持一个真实的工程需求:将运行中的提示词版本与事件关联起来。这是一个合理的决定。如果没有它,在故障期间你无法回答“哪个版本的系统提示词产生了此答案”,也无法有把握地废弃一个糟糕的提示词。

可观测性团队将该字段摄取到追踪存储(trace store)中,方式与摄取其他每个字段相同——按类型。它是一个字符串。字符串有一个配置好的脱敏器(redactor),用于擦除电子邮件地址、信用卡模式以及任何符合 PII 正则表达式的内容。脱敏器无法识别系统提示词,因为系统提示词看起来不像 PII。它看起来像产品文案。

企业团队根据不同的路线图,扩展了客户可见的审计摘要,以展示请求级别的字段,以便租户能够满足他们自己的审计人员。白名单是由了解客户需求的业务经理(PM)构建的。他们加入了 llm.request.system,因为运行多模型评估的租户希望看到他们的查询命中哪个提示词。PM 将其视为调试字段。客户的审计员将其视为记录在案的供应商行为。从各自的角度来看,两者都是正确的。

这三名评审员都没有全局视角。平台团队认为日志是工程师可见的。可观测性团队认为敏感性意味着 PII。企业团队认为摘要的范围仅限于租户。没有人明确谁该负责“系统提示词在所有这些层面中是否敏感?”这个问题,因为这个问题跨越了每个团队的边界。

OWASP LLM Top 10 中关于系统提示词泄露的内容明确告诉你要将系统提示词视为潜在公开的,且绝不要将其作为安全控制手段。对于提示词所包含的内容,这是个好建议。但在涉及到提示词本身是否构成泄露的问题时,这个建议并无帮助。拒绝策略之所以敏感,并不是因为它隐藏了凭据。它之所以敏感,是因为它属于产品表面——即你的助手如何拒绝、拥有哪些工具、会避开哪些话题,以及你的检索流水线认为哪些内容是可检索的编码形式。这是具有竞争力的资料。有时它还涉及监管要求。

为什么脱敏器漏掉了它

PII 脱敏器是基于“敏感数据具有可识别的表面形式”这一假设进行训练的。电话号码有特定形状。电子邮件有特定形状。甚至 API 密钥和 JWT 都有可识别的前缀和熵分布。基于正则和机器学习的脱敏器之所以能达到一定的精度,是因为它们利用了这些结构化先验。

系统提示词的结构化先验是“用你产品语气的指令”。它与文档、营销常见问题解答或支持宏(support macro)无法区分。脱敏器看到的是看起来像内容的东西,然后就放行了。来自 OpenTelemetry 社区的安全可观测性研究一直在推动分层、具有驻留意识(residency-aware)的分类,正是因为正则时代无法解决这个问题——敏感性是字段来源(provenance)的属性,而不是字段形状的属性。

这就是为什么字段类型的敏感性标签是目前最接近真实修复的机制。创建 llm.request.system 的平台团队应该被要求在定义字段时附加分类,而不是在写日志时。分类应该是 confidential-product(机密产品)或 model-attribution-only(仅限模型归因)或者任何你的分类体系——但缺失标签应该默认拒绝,而不是默认允许。

Datadog 和类似的日志平台已经发布了限制访问日志路径的 API,但这种限制是选择性开启(opt-in)的。添加字段的团队必须记住该字段是敏感的,并配置限制。记忆并不是一种安全控制。将未知字段默认为最宽松的级别,正是产生裂缝的原因。

组合规则

一个更深刻的教训是:两个关于可见性的独立且正确的决策,组合起来后可能演变成一次信息泄露,而你现有的评审流程并非为了捕获这种“组合”而设计的。

扩展审计流(audit feed)白名单的变更请求通常只是一个小工单。评审者会问:这些字段分享给租户安全吗?他们孤立地观察每个字段,看到它是一个字符串,看到该字段已经应用了 PII 脱敏,看到数据分类工具没有发出告警,于是予以批准。而最初负责 llm.request.system 变更的评审者会问:这个字段在内部记录日志安全吗?他们确认了其中不包含用户 PII,确认了工程师在处理故障排查时需要它,于是也予以批准。

这两位评审者都不是针对“组合结果”的评审者。在你的组织架构中,没有也不应该有“审计流 × LLM 字段”的评审员——你无法为功能的每一个笛卡尔积都增加一名评审员。这种机制必须依靠字段自身的分类,并让分类随字段跨系统流动。要像对待类型签名一样对待敏感度标签:一个接收来自另一系统字符串的函数,除非类型告知,否则它无法理解该字符串的含义,而“string”并不是一个有用的类型。

当提示词成为产品界面时,会发生什么变化

如果你接受系统提示词(system prompts)是产品的一部分——即它们像配置文件一样编码了行为——那么一系列决策将会随之改变:

系统提示词将变成受版本控制的产物,拥有与鉴权层(auth layer)相同的评审纪律。它们会有差异对比(diff),会有变更日志条目,会有归属说明。你可能已经在非正式地这样做。将其正式化意味着提示词有一组已知的消费者——你的推理层、评估框架、A/B 测试框架——任何新的消费者都需要经过安全评审,就像为你的会话令牌(session-token)表添加新消费者一样。

引用提示词的日志字段在定义时就需要贴上敏感度标签。如果一个新的字段名称匹配 llm.request.*agent.system.* 模式且没有标签,CI 检查将拦截该 PR。该检查不需要理解字段的语义,它只需要强制执行:让进行变更的工程师做出明确的分类决策。PR 描述会将这一决策带入评审环节。

面向客户的审计流和面向工程师的链路追踪存储(trace store)拥有分别维护的白名单,并且两者都遵循“默认拒绝”原则。向其中任何一个添加字段都是一种刻意行为。负责审计流的团队拥有关于租户应该看到哪些字段的文档化流程,且该流程明确列出“任何 LLM 请求或响应字段”都需要进行分类评审。

此前专注于推理时提示词提取(prompt extraction)的红队演练,现在也要针对你面向客户的审计流、日志导出和追踪 UI 进行。红队脚本非常简单——在任何租户可访问的导出内容中查找具有提示词特征的字符串——它能捕获那些绕过你推理层缓解措施的精确失效模式。如果你现有的渗透测试覆盖范围不包括这一点,增加它的成本仅需一个工程师日。

事件复盘将重新发现的架构教训

这一类别下的每一份事故后复盘(postmortem)最终都会得出类似结论:泄露过程不需要攻击者。组合后的系统替攻击者完成了本该由他们做的工作。模型从未提取过提示词,因为根本没人问它;是审计流直接把提示词交了出来。

复盘报告中往往倾向于建议缩减审计流白名单并使用更智能的脱敏工具。作为即时补救措施,这两者都是正确的。但它们都没有解决底层属性:即 LLM 应用中接触提示词的表面(surface),要比非 LLM 应用中接触配置文件的表面多得多。每一个可观测性工具、每一个重放框架、每一个评估流水线、每一个 A/B 日志记录器都能看到提示词,因为提示词本身就是让调用变得具有记录价值的原因。其中每一个都是潜在的泄露路径,而每个路径的评审者又各不相同。

架构层面的举措是将分类推向字段的起点,并强制其随之流动。发送 llm.request.system 的记录器应该调用一个需要敏感度标签的 API,如果缺少标签则拒绝发送。追踪存储应该拒绝为超过特定模式阈值的未打标字段建立索引。审计流应该拒绝展示超过特定敏感度等级的字段,除非针对工单记录了明确的租户导出批准。这些都不是新颖的机制。这是你处理会话令牌、支付数据和 PII 的既有方式。转变在于:意识到提示词也属于此类范畴。

提示词提取不是因为攻击者变聪明了才产生的“研究课题”。它是一个架构问题,因为你的系统提示词是极具价值的产品资产,目前却被当作调试字符串对待。你所在行业的下一次事故将是以下两者之一:客户在审计流中发现了该字段并悄悄归档,或者竞争对手从他们付费获取的日志导出中读到了你的拒绝策略。前者你会在支持队列中看到;后者你可能永远都发现不了。在两者发生之前消除这一差距的团队,是那些已经开始像分类令牌一样分类 LLM 字段的团队——根据字段产生的位置分类,而非根据字段看起来的样子。

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