Skip to content

敏感数据与信任边界 ​

页面可以展示一个值,但不必把这个值交给 Agent。这句话看起来简单,却把五个不同的问题混在了一起:页面运行时可以读取什么、什么会进入模型请求、屏幕前的人看到什么、存储层保留什么,以及后端授权什么。回答其中一个问题,不会自动回答另外几个。

前面已经说明了观察中包含什么。本章把范围收窄到观察中的敏感值,以及这些值可能离开浏览器的各条路径。

模型请求才是边界 ​

页面运行时可以读取应用声明给它的值。模型不能检查 DOM、读取 Cookie 或查询应用 store。它收到的是一份由运行时组装的请求:系统提示词、当前页面观察、最近的对话和工具结果。

一个值一旦进入这份请求,就已经离开了浏览器。后续指令无法让模型忘记它。历史压缩可能把它写进摘要,模型服务商可能记录它,下一轮请求也可能再次发送它。因此,真正的问题不是“这个字段是否敏感”,而是“这个字段的哪一种表示必须跨过边界”。

下面几个问题经常被混在一起:

范围要回答的问题由什么控制
运行时输入运行时可以读取什么?声明的记录、状态视图和敏感值标记
模型请求离开浏览器的是什么表示?敏感值声明与有界序列化
屏幕人看到的是什么?展示层渲染,不是模型的访问权限
存储刷新后还留下什么?连续性快照脱敏与存储策略
后端实际允许发生什么?能力范围、确认策略和后端授权

“模型看不到这个值”不等于“这个值没有离开浏览器”,也不等于“这个值没有被存储”,更不等于“模型不能触发使用这个值的操作”。每一种说法都需要各自的机制。

页面上同时存在四类数据 ​

观察中不只有业务记录。评审时区分下面四类文字和数据,会更容易看清信任差异。

类别例子处理方式
作者语义动作说明、区域名称、状态说明、输入文档可信任其含义,但不能当作权限;不要用它承载秘密
页面数据记录字段、状态值、反馈、动作结果、用户生成内容作为数据处理,不能当作指令;敏感值必须显式标记
用户输入在助手中输入的请求,包括从别处复制来的值用户意图;进入模型历史前,替换已知的敏感字面值
凭证与会话材料Cookie、JWT、Authorization 头、模型服务密钥、后端秘密不进入观察,也不进入页面模型;留在拥有它们的边界内

记录字段里可以有一句看起来像系统指令的话。它仍然是页面数据。一段写得很好的动作说明可以陈述业务规则,但它仍然不授予权限。这个区分可以限制提示注入,但不能替代能力设计、确认和后端授权。

敏感性是声明的,不是猜出来的 ​

参考运行时不会猜测哪些字段是手机号、证件号、薪资、地址、私人备注或内部标识。它提供一个标记,由应用在构造记录或状态视图的地方显式使用:

ts
fields: {
  customer: order.customer,
  phone: sensitive(order.phone, "token"),
  idCard: sensitive(order.idCard, "masked"),
  internalRating: sensitive(order.rating),
}

字段名仍然保留。模型可以知道这里有手机号,可以判断哪项操作需要它,但不会收到号码本身。这个机制的目的不是从业务模型里删除字段,而是替换字段出站时的值。

四种模式覆盖大多数情况:

模式模型收到动作能否使用暴露了什么
hidden(默认)[hidden]不能字段存在,字段名可能说明含义;不再披露值或形状
masked138****1234 这样的形状不能;掩码不是原值部分形状,通常够核对尾号
token{{p:1w9el7a}} 这样的稳定引用能,原样使用一个不透明引用,不是底层值
hint[mobile number] 这样的说明不能类型,但不给形状

默认是 hidden,不是 masked。还没想清楚要暴露多少时,应当少暴露。部分值仍可能识别出一个人,或缩小搜索范围。token 是唯一为“Agent 必须把值传给动作”而设计的模式。

这是声明机制,不是数据防泄漏系统。没有标记的字段会像普通字段一样被序列化,只受观察的大小限制。

一次 token 如何穿过运行时 ​

假设一条订单记录里有客户、手机号和证件号。客户名可以作为普通页面数据,手机号需要被动作使用,证件号只需要部分形式供人核对。

ts
{
  id: "SO-1042",
  fields: {
    customer: "Globex",
    phone: sensitive("13800001234", "token"),
    idCard: sensitive("110101199001011234", "masked"),
  },
}

模型可能收到:

text
Record SO-1042: customer=Globex; phone={{p:1w9el7a}}; idCard=110***********1234

用户要求给这位客户打电话时,模型可以返回:

json
{ "record": "SO-1042", "phone": "{{p:1w9el7a}}" }

运行时会依次做几件事:

  1. 解析参数。
  2. 扫描参数里的 token 形状字符串。保险箱里不存在的 token 会以 INVALID_ARGS 拒绝,处理函数不会运行。
  3. 在参数校验之前,把已知 token 还原成真实值。
  4. 校验还原后的参数,并进行确认决策。
  5. 把真实值传给页面声明的处理函数。
  6. 模型历史和保存的连续性数据里保留 token,而不是真实值。

界面可以把 token 还原成真实值,展示给正在使用页面的人。这个展示步骤与模型请求是分开的。界面能显示真实值,不代表模型也会收到它。

三个性质需要记住:

  • 同一个运行时会话内稳定。 同一个值得到同一个 token,模型才能跨轮次引用同一条记录。
  • 新的运行时重新生成。 创建运行时时会使用新的随机盐,刷新文档后引用会改变。
  • 不是加密方案。 token 是运行时持有的不透明引用,不是密文,也不是授权凭证。应当把它当作敏感引用,而不是可以安全转发到别处的秘密。

token 也不能替代确认。如果一个动作接受 token,而模型被说服去调用它,运行时仍然可以还原值并执行操作。阻止不该发生的操作,靠的是能力边界、确认策略和后端自身的授权。

人和模型看到不同的表示 ​

敏感值处理把模型表示和展示表示分开。

对于 token 值,参考界面在渲染聊天文本和运行页时会还原真实值。面向模型的历史、连续性快照和下一次模型请求里仍然是 token。事件流是混合的界面数据:用户消息保留用户输入的原话,模型和工具事件携带 token。对于 hidden 和 masked,没有可供还原的存储值;展示层看到的就是模型收到的有限表示。

这也是模型不能解释“这个字段已脱敏”的原因。它把 token 当作字段值。如果它说“号码被隐藏了”,就会出现与用户界面相矛盾的陈述。系统提示要求模型把 token 当作普通值使用,不要猜测或编造其内容。

用户输入遵循同一条出站规则。如果用户输入了一个已经登记在保险箱里的值,模型历史会收到 token,而界面保留用户的原话。保险箱从未见过的值无法用这种方式识别。

展示层还原不是安全边界。它只决定助手界面显示什么。页面本身仍然可能显示真实值,浏览器开发者工具可以检查它,屏幕录制也可以捕获它。在助手界面隐藏还原值,对演示、共享屏幕和录屏有用;但它不会让浏览器环境变成保密的。

每条出站路径都要用同一条规则 ​

敏感性不只是记录字段的属性。真实值可以从多条路径离开。

出站路径运行时做了什么仍然存在的限制
记录、状态和页面反馈渲染已标记的值;替换反馈中已知的 token 字面值只保护已标记或已知的值
工具结果和错误信息进入模型历史前,替换已知的 token 字面值精确匹配;派生值或改过格式的值识别不到
用户输入加入历史前,替换已知的 token 字面值未知值保持不变
连续性与本地检查点保存前,在快照和事件中替换已知字面值不会清理无关的应用存储

这套替换刻意保守。它按长度从长到短替换已知值,避免短值先吃掉长值的一部分。它不是通用的个人数据分类器。它无法识别由两段文本拼出来的手机号、被渲染进图片的值,或从未登记进保险箱的值。

由此得到一条重要规则:hidden 和 masked 单独使用时,不会建立替换字典。真实值没有留在保险箱里,所以后续错误信息如果包含这个真实值,就无法被识别和替换。当同一个值可能出现在工具结果、错误、反馈或用户输入中时,应当使用 token。

日志也适用同样的区分。应用审计应记录能力、目标标识、决策、结果和安全摘要,不应因为运行时为了执行而还原过参数,就把原始参数一起复制进去。

这套机制不保护什么 ​

下面这些限制应当在设计评审和事故预案里写清楚。

  • 没有标记的字段就是普通数据。 简写的记录声明会把所有字段序列化,不会推断哪些字段敏感。
  • 作者控制的元数据仍会发出去。 动作说明、标签、id 和作用域名称都是作者语义。不要把凭证或个人值写进去。
  • 精确替换不理解派生。 拼接、格式变化、翻译后的值和部分重建都超出它的能力。
  • 图片和截图不在数据模型里。 如果未来的客户端发送像素而不是声明数据,本章的敏感标记不会覆盖这些像素。
  • 模型服务商和网关是独立信任域。 Pling 可以控制自己发送什么,但不能保证服务商的保留策略、网关的调试日志或第三方可观测系统如何处理请求。
  • 浏览器环境由用户控制。 开发者工具、扩展、远程调试和被攻破的设备都可以读取页面原始状态。token 化不是数字版权管理。
  • 提示注入仍然相关。 页面数据可以试图说服模型滥用 token。模型仍然看不到真实值,但如果周围的控制允许调用,它可以在已声明动作里使用这个引用。
  • token 不是密码学保证。 参考实现使用会话级引用,不是加密。它的目标是让值不进入模型上下文,不是充当通用秘密存储。
  • 后端授权仍然是最终权威。 一个看起来无害的 token 化字段,不会授予读取资源、调用动作或跳过业务规则的权限。

最后一点最容易被忽略。敏感值处理改变的是披露,不是权限。

信任是分布的,不是一次授予的 ​

一个页面应用至少涉及六方:页面作者、页面运行时、页面数据、模型服务商、使用页面的人和拥有业务操作的后端。它们不在同一个信任层级上。

页面作者决定页面暴露什么。运行时执行声明的表示与执行路径。页面数据可能由用户写入、从第三方导入或从不可信文档复制而来,它不是指令来源。模型是对请求进行推理并返回建议的外部服务。人可以看到页面展示的内容,但不一定是正确的业务授权人。后端决定操作是否允许。

凭证属于拥有它的边界。浏览器不应把 Cookie、Authorization 头或后端秘密放进观察。放在浏览器代码里的模型服务密钥对该浏览器的使用者可见;生产部署通常把这把凭证放到服务端网关后面。网关负责验证用户和模型请求,但不会让页面数据变得可信。

外部 Agent 不是例外。它通过页面能力接口收到同一份声明表示,页面运行时只在调用已声明动作时还原 token。外部客户端不会拿到 token 到真实值的映射。它仍然需要有效会话、允许的作用域,以及操作所需的后端授权。

一次评审清单 ​

在把页面视为可以处理敏感业务数据之前,先回答这些问题:

  1. 哪些字段可以识别一个人、暴露财务或医疗状况、泄露内部决策,或充当凭证?
  2. 对每个字段,Agent 需要真实值、部分形式,还是只需要知道字段存在?
  3. Agent 必须传入动作的每个值,是否都声明成了 token?
  4. 动作说明、标签、id、作用域名称和错误文案里是否没有秘密?
  5. 工具结果、错误、页面反馈和用户输入是否都经过能替换已知值的路径?
  6. 测试是否检查了真实的模型请求体,而不只是聊天界面?
  7. 刷新页面是否会产生新 token,而同一个会话内同一个值是否保持稳定?
  8. 审计、分析埋点和连续性存储里是否没有原始敏感值?
  9. 展示层还原是否是为了共享屏幕、录制和演示而有意保留的?
  10. 当模型提供有效 token 时,后端是否仍然验证身份、作用域和授权?

小结 ​

敏感值处理是一条表示边界。页面运行时保留真实值,模型收到声明的替代表示,用户可以通过界面看到真实值,存储层对保险箱已知的值保存引用。这是四个不同结果,必须分别验证。

没有任何一个标记能让页面自动变安全。应用仍然要声明字段、把凭证留在作者元数据之外、设计窄能力、在后果需要时加入确认,并在后端执行授权。敏感值模型的贡献更窄,但仍然必要:Agent 可以知道一个值是用来做什么的,在需要时把它传入已声明动作,并在真实值不进入模型上下文的情况下完成任务。