智能体轨迹里的截图与 DOM 产物。
你放上生产的第一个浏览器智能体,会把你的轨迹存储变成一座图像档案馆,而你为它建的每一项控制都假定内容是文本。一个在提示词上有 99% 召回率的脱敏器,在一张 PNG 上的召回率是零;一套为 JSON span 写的留存策略,遇上的是大三个数量级的产物;而那一帧画面同时是模型的输入、是你向审计方证明这次运行确实做了你所说之事的唯一证据,也是一条没人在扫描的注入通道。出路是不再把所有捕获当成同一类产物:把无障碍树作为文本正本来采集,在像素离开页面之前就在页面内做遮罩,并且只在真正作出了决定的那些步骤上保留画面帧。
一帧画面不是一个 span 属性,而数字就是原因。
崩掉的先是经济性,不是隐私。一次工具调用的文本 span 是几 KB。一张现实视口下的整页截图是 300 KB 到 3 MB,而浏览器智能体每观察一次就生成一张——于是一段五十步的会话,在任何人看过它之前就已经是几百 MB。
- 你的可观测性账单会给自己重新定价。多数追踪后端按接入量计费,而图像负载会在上线后一周之内主导那个数字。团队通常是以「一张意外的发票」而不是以一次设计决策的形式发现这件事的。
- 大块二进制不该待在 span 里。把画面帧放进对象存储、给它自己的生命周期规则,在 span 上只带一个引用加一个内容哈希。正是这次拆分,让你之后能用三十天的时钟清掉图像,同时把轨迹留一年。
- 按产物类别定留存,而不是按整条轨迹。整条轨迹一个 TTL,会逼你在「留着一页你永远不会打开的像素」和「丢掉你真正需要的工具序列」之间做选择。价值不同、风险不同的类别,应该有不同的时钟——这和采样与留存讲的是同一个道理,只是现在多了一项真的会咬人的成本。
- 按整段会话采样是选错了轴。只留 5% 的会话,等于丢掉 95% 的失败证据。留 100% 的会话加 10% 的画面帧,几乎不会丢掉任何你真会用到的东西。
把无障碍树作为文本正本来采集。
单项杠杆最大的决定,是你把哪种表示当作权威表示——而那通常不是像素。智能体用的几套浏览器驱动栈里,有几个——其中 Playwright 的 MCP 服务器最为明确——是在无障碍树而不是截图上行动的,这意味着模型据以推理的那样东西,本来就已经是结构化文本。
- 文本可脱敏、可 diff、可 grep。一份无障碍快照走的是和别的一切一样的脱敏器,在步与步之间能干净地 diff,也让你不必动用视觉模型就能搜索「哪些会话看见了这个按钮」。
- 它也是对「智能体感知到了什么」更诚实的记录。如果智能体是按角色和名称选中元素的,那截图只是给人看的一次重绘;树才是输入。只存截图,等于让你的证据和智能体的真实输入是两样不同的东西。
- 把快照、步间 diff 和画面帧哈希都存下来。有了哈希,即便图像本身已经过期,你之后仍能证明某一帧属于某一步。
- 整份 DOM dump 是两头都差。几 MB 的标记,而且里面带着用户从未看见渲染出来的隐藏输入、内联 JSON 负载、data 属性和 CSRF 令牌。如果你确实需要标记,请围绕被操作元素采集一棵有界的子树,而不是
document.documentElement.outerHTML。 - 视觉驱动的智能体是例外,并要为此付费。如果智能体是靠像素行动的,那画面帧就是输入,你没法把它降级——于是下面那些控制从可选变成必做。
在页面内部遮罩,因为像素没有脱敏器。
文本追踪那条规则原样成立——未脱敏的产物绝不能跨出进程边界——但实现必须挪进浏览器里,因为一帧画面一旦编码完成,下游什么也修不了。先 OCR 再脱敏,是在第一个召回率问题上又摞了第二个检测器的召回率问题,而且它跑在导出之后。
- 用截图 API 自带的遮罩能力。Playwright 这类工具接受一组选择器,在编码之前把那些区域涂掉,于是敏感区域压根不会进到缓冲区。请按应用维护这份选择器清单,放在代码里,像任何别的控制那样被评审。
- 更好的做法是用应用自己拥有的 CSS 类。让产品侧去标出敏感区域(
.pii、data-sensitive),你遮罩那个类。这样产品团队新加的字段会自动继承遮罩,而不是悄悄出现在捕获里。 - 失败要闭合,而且要吵。如果某个遮罩选择器什么都没匹配上,那要么是布局改了,要么是改版了——两种情况都该丢掉这一帧并报警,而不是送出一张没遮住的捕获。选择器腐烂是这里可预期的失效方式。
- 别忘了屏幕上还有什么。一帧画面会捕获所有渲染出来的东西,包括那条通知提示、列表里的另一条记录,以及客服本人的姓名。文本脱敏器从来不必考虑「旁人」;截图必须。
- 会话隔离是一项隐私控制,不只是卫生习惯。驱动一个干净自动化配置档的智能体,捕获到的是一张没有任何已登录身份的页面;驱动真实配置档的智能体,捕获到的是那个账户。这个选择在你挑浏览器栈的那一刻就做完了,并且在任何遮罩存在之前就决定了你的捕获有多糟。
- 交互轨迹泄露的方式不一样。按键级日志和
fill()的参数里装着密码字段的内容,跟截图有没有装无关。请用同一份清单去脱敏动作日志——这条流水线接进哪里,见从智能体轨迹中脱敏 PII。
请把它当作一项控制来测试,而不是假定它有效:拿你真实的测试套件去跑一个种入了标记诱饵值的预发应用,然后在导出的产物里 grep 这些值。命中任何一条,就是一次带着调用栈的泄露。这是唯一一种能在布局变更后依然有效的保证手段,接进 CI 只要一个下午。
在「作出了决定」的地方留帧,而不是在「走了一步」的地方留帧。
均匀捕获既是最贵的策略,也是最没用的,因为能解释失败的那些帧聚得很紧,其余的只是滚动位置。
- 写操作前的那一帧永远留。每一次不可逆的动作——提交、下单、删除、发送——对应的动作前捕获,都按长 TTL 保留。这就是回答「智能体点下去的时候,它以为页面上写的是什么」的那份证据。
- 失败或中止运行的最后一帧永远留。便宜、体积极小,而且是任何分诊的第一个落脚点。
- 一旦你能检测到首次偏离,就留住它。某一步的无障碍 diff 异常地大、某个选择器没命中、某次没人要求的跳转。这些步骤上的帧,比它们之前的二十帧加起来都值钱。
- 其余的要激进且不对称地降采样。成功运行中段用缩略图分辨率的帧,仍足以让人确认路径;结果本来就对的时候,全分辨率什么也买不到。
- 在过期之前先做晋升。一份进入回归用例的捕获会搬进评测数据集,并比轨迹活得更久——所以它必须在晋升时重新遮罩一遍,因为评测集不会从它出身的存储里继承任何留存规则。
这件产物同时也是输入,于是这个存储成了攻击面。
这是在普通追踪里找不到对应物的部分。一张恶意页面的截图,是注入负载的一份忠实副本,而它落进的那个系统,当初是按「只供人只读查看」设计的。
- 回放会把负载重新喂回去。把录制的会话重跑一遍,等于把那条被注入的指令交还给一个此刻握着当前凭据的智能体。浏览器轨迹的回放应当放在一个够不着生产的环境里——和回放测试讲的是同一条纪律,只是理由更锋利。
- 你的裁判和摘要器也在读它。LLM 作裁判、自动分诊摘要器,或者一个「描述一下这次失败」的小助手,都是把捕获到的页面当输入在消费,并且可以被它操纵。请把每一次消费产物的模型调用,都当作在读不可信的遥测。
- 在仪表盘里渲染它本身就是风险。在内部工具里展示捕获到的标记,等于从你自己的源站送出第三方 HTML。请把捕获渲染成图片或转义后的文本,绝不要渲染成活的 DOM——这正是安全渲染智能体输出的论点,只是现在用在轨迹查看器上。
- 每件产物都带出处。在引用旁边记下 URL、画面帧哈希和一个信任级别,好让下游消费方能分清:这是你自家应用的捕获,还是某个任意站点的捕获。
第一个浏览器智能体上线之前该就位的东西。
- 大块二进制搬出 span。画面帧进对象存储、有自己的 TTL;span 只带引用和哈希。跑过一个季度的流量之后再补,那是一次迁移,不是一次改配置。
- 每步一份无障碍快照作为文本正本,走你现有的脱敏器,画面帧作为次级产物。
- 一个由应用拥有的遮罩类,加一份按应用维护的选择器清单,在捕获时生效、失败闭合,并在 CI 里配一个诱饵测试。
- 一张写下来的留存矩阵。写前帧与失败帧留长;成功的中段留短并降成缩略图;DOM 子树留得最短。
- 一条「谁可以打开一帧画面」的规则。一张客户屏幕的捕获配得上破窗待遇,而不是聊天频道里的一个链接——这正是数据治理本来就要求的访问模型。
如果这周只做一件事,那就让无障碍快照成为你分诊流程第一个打开的产物,把截图降为辅助证据。别的都跟着这次反转自然发生:正本变成了文本,于是你现有的脱敏、留存与搜索全都重新适用;图像变成一个经过采样、遮罩、短命的层级,你能给它定价;而「这个我们能留着吗」不再是关于一团不透明二进制的一个决定,而是四个你真能为之辩护的小决定。
相关:追踪与可观测性讲底下的 span 设计,浏览器智能体讲产出这些产物的那套栈,计算机操作讲为什么一个像素驱动的智能体没有更便宜的记录可用。