一个计算机使用型智能体(computer-use agent)在客户的 CRM 上完成了一项任务,工作线程池将浏览器返回到空闲环中,几百毫秒后下一个请求到达,仪表板导航成功——唯一的问题是,它是作为错误的用户登录成功的。前一个会话的 OAuth cookie 仍留在配置文件(profile)中。追踪记录显示 navigation succeeded(导航成功)、screenshot captured(截图已捕获)、action performed(操作已执行)。运行日志中没有任何内容表明,智能体正在以一个从未授权过它的用户身份进行操作。
这是浏览器智能体从其构建所用的库中悄然继承的一类故障。无头浏览器(headless browser)框架被设计为每个配置文件仅供一个用户使用,因为这是浏览器三十年来的工作方式。当工作池为了摊销全新的 Chromium 实例长达八秒的冷启动时间而重用配置文件时,这种“单用户”假设就破裂了,而且这种破裂对于团队通常信任的每一层遥测数据来说都是不可见的。
为什么要重用配置文件
冷启动一个干净的浏览器代价高昂。一个加载了智能体所需的所有扩展程序的全新 Chromium 进程需要几秒钟才能渲染出第一个页面;对于一个延迟预算低于两秒的面向用户的智能体来说,这并不是一个你可以在每个请求中支付的启动成本。因此,工程上的本能反应与自连接池诞生以来每个 Web 平台所使用的反应是一样的:保持一个包含 N 个浏览器的热池(warm pool),每个请求分配一个,请求完成后将其归还。
问题在于,数据库连接池中的“连接”除了 TCP 套接字之外没有持久状态。而浏览器池中的“浏览器”拥有三十年积累的状态原语——cookie、localStorage、sessionStorage、indexedDB、service-worker 缓存、HTTP 缓存、自动填充数据、扩展存储、下载的文件、密码管理器状态。浏览器自身的设计假设有一个人类坐在它面前,它提供的每一种状态机制都是为了服务于该人类的会话连续性。在不同请求之间重用配置文件,意味着你在没有告知浏览器的情况下颠覆了这一假设。
这种重用模式以几种形式出现,但都具有相同的潜在风险:
一个长期运行的 Playwright BrowserContext 实例池,每个实例被分配给队列中的下一个请求。
一个完整的浏览器进程池,其 --user-data-dir 指向一个持久目录,以便登录会话在重启后依然存在。
支持计算机使用型智能体的“热车队”(warm fleet)服务,其池大小根据峰值并发量确定,且配置文件在请求之间可能会保持数小时的活跃状态。
在每种情况下,前一个租户积累的凭据在下一个租户的请求开始时,仍然留在磁盘和内存中。
浏览器在调用之间携带的状态面
那些没有处理过无头浏览器安全方面的工程师,往往会低估执行 page.close() 后残留的内容。清单比看起来要长:
Cookie ,包括 HTTP-only 和安全 cookie——它们属于配置文件,而非选项卡。
前一个请求涉及的每个源(origin)的 localStorage 和 sessionStorage 。
IndexedDB ,SaaS 应用越来越多地在这里存放 JWT、会话元数据和离线缓存。
Service-worker 缓存 ,它可以在不访问网络的情况下提供前一个租户的经过身份验证的响应。
HTTP 缓存 ,它可以绕过重新认证流程,并从磁盘提供已登录的仪表板。
扩展存储 ,包括密码管理器的自动填充数据和 SSO 辅助状态。
下载的文件和文件选择器的工作目录 。
自动填充配置文件 ——姓名、地址、支付方式——下一个请求可能会意外地将其提交到表单中。
请求之间的“会话边界”必须清除上述每一项,而不仅仅是 cookie 罐。大多数自研的重置程序只清除前三项,而忽略了其余项。尤其是 Service-worker 缓存很容易被忽视,因为它们是页面离线行为的一部分,而不是明显的“会话”层面。
像 Playwright 这样的框架明确说明,创建一个全新的 BrowserContext 是受支持的隔离原语——上下文的设计初衷是创建成本低廉,且在单个浏览器进程中彼此完全隔离。陷阱在于,从测试领域学习这种模式的团队往往会将其错误地映射到他们的智能体基础设施上:在测试中,框架会为每个测试创建和销毁上下文,因此隔离是自动的。而在智能体工作池中,该生命周期必须由团队强制执行,且“我们使用 Playwright 上下文”并不等同于“我们为每个请求销毁并重建上下文”。
追踪记录未显示的故障模式
当发生会话泄露(session bleed)时,智能体自身的观测系统不会提示任何错误。导航返回了 200。页面已渲染。截图已捕获。DOM 操作已成功。模型根据其看到的内容生成了连贯的响应。从智能体的角度来看,该请求是成功的。
追踪记录没有显示的是,页面是以错误的身份(principal)渲染的。这里出现了三种具体的故障模式:
读取泄露。 下一个请求要求智能体“总结我未解决的工单”,而配置文件仍作为另一个租户通过了身份验证,智能体读取了该租户的工单队列,对其进行总结,并将摘要返回给一个本不该看到这些内容的用户。原租户不知道他们的数据刚被读取;接收租户不知道这些数据不是他们的。如果接收用户注意到了,你会收到一封令人困惑的支持工单。如果没有注意到,泄露就是永久性的。
写入污染。 下一个请求要求智能体“将交易状态更新为成交(closed-won)”,配置文件已作为另一个租户的账户通过了身份验证,于是智能体将更新写入了错误的 CRM。目标租户的审计日志显示了一个真实身份进行的合法编辑——只不过该身份是在代表别人的公司发出请求。恢复工作需要进行手动对账,而没有任何系统被设计为支持这种操作。
意外的权限提升。 前一个租户恰好是工作区管理员;下一个请求来自一个普通用户。配置文件仍作为管理员身份通过验证。在该请求期间,智能体现在拥有管理员权限,安全审查中任何“智能体只做用户能做的事”的假设都被工作池证伪了。
在这三种情况下,现有的观测堆栈都报告正常(显示绿色)。延迟正常。成功率正常。在隔离的测试固件上运行的评估套件(eval suite)没有显示任何退化。只有当外部方注意到数据错误时,该漏洞才会浮出面。
真正的隔离是什么样的 必须落实的准则很简单,但在实践中却极其严苛:浏览器会话的生命周期必须镜像单元测试的生命周期,而不是长期运行的工作进程 (worker) 的生命周期。几个具体的选择使之成为现实。
默认按请求创建配置文件 (profile)。 将冷启动成本视为正确性的成本。如果延迟预算无法承受,请将其视为架构错误的信号,而不是放松隔离的信号。提供基于 microVM 的浏览器会话的沙盒供应商在 2026 年已将冷启动时间压低到 1 秒以内,这消除了以往重用配置文件的绝大部分借口。
经过验证的会话边界。 当出于成本考虑必须放宽隔离时,不要相信浏览器的“无痕模式”或 clearCookies() 抽象就足够了。构建一个会话边界原语 (primitive),显式擦除 cookie、localStorage、sessionStorage、indexedDB、service-worker 注册及其缓存、HTTP 缓存、扩展存储和下载目录——然后在声明边界完成之前,针对已清除的状态执行 验证读取 。静默失败的重置比没有重置更糟糕,因为它会产生虚假的信心。
带硬边界的租户标记配置文件池。 如果配置文件池不可避免,请按租户对其进行分区,并拒绝跨分区访问。身份验证为租户 A 的配置文件可以重用于租户 A 的另一个请求;无论出于何种成本原因,它绝不能被租户 B 重用。这能将“跨租户会话泄漏”从运行时风险转变为部署时的不变量。
执行每项操作前的权限主体检查。 在智能体 (agent) 执行任何读取或写入操作之前,请验证页面的已认证会话是否与发出请求的权限主体 (principal) 相匹配。如果页面报告的已登录用户与请求预期的权限主体不符,请立即中止并进行审计。这是捕捉上游隔离遗漏的所有问题的安全带。
以凭据而非请求为键的审计日志。 当事件发生后的问题是“此请求是否以错误的权限主体执行”时,唯一有用的答案来自一个在请求开始时准确记录配置文件中存在哪些凭据的日志。如果该日志不存在,团队就只能通过页面加载时间戳和截图 OCR 进行取证重建,而答案通常是“我们无法确定”。
为什么安全审查漏掉了这一点 在大多数团队中,浏览器智能体的安全审查是逐个工具进行的。每个工具——navigate、click、type、screenshot——都被孤立地检查,结论是它们单独都不危险,然后智能体就上线了。逐个工具的审查从未问过的问题是:浏览器本身 在一个工具调用到下一个工具调用之间携带了什么状态。因为在智能体的思维模型中,浏览器不是一个工具——它是所有工具执行的运行时 (runtime)。
这与团队从无状态 API 的思维模式切入有状态执行环境时遇到的鸿沟是一样的。浏览器不是一个函数调用。它是一个拥有磁盘、内存、网络状态和会话模型的进程,而该模型的连续性正是其设计的核心。将其视为函数调用正是导致数据泄漏的原因。
采购现实进一步加深了这一鸿沟。无头浏览器库默认选择“重用配置文件”,因为这样抓取速度快,而计算机操作智能体堆栈则默认继承了这些设置。供应商文档通常将配置文件重用描述为一种性能特性,而不是安全性折中。等到安全团队提出疑问时,这种架构假设已经深入到了一年的产品代码中。
架构层面的觉醒 浏览器是一个有状态的执行环境,拥有三十年来为每个配置文件一个用户而设计的会话管理原语。让一个配置文件运行 N 个租户的智能体是在违反浏览器自身的设计——随之而来的数据泄漏类漏洞是结构性的,而非实现层面的。在智能体循环内部进行多么精细的编码都无法掩盖一个从未为这种工作负载构建的隔离模型。
做对这一点的团队将浏览器隔离视为平台的一部分,而不是智能体的一部分。他们投资于按请求分配的会话生命周期、经过验证的会话边界原语、重用不可避免时的租户分区池,以及一个失败即关闭 (fail closed) 的运行时权限主体检查。他们在发生事故前就建立好审计日志,因为一旦事故发生,问题将无从解答。
做错的团队仍然运行着他们的热启动池 (warm pool),并且仪表盘上依然显示着一片绿色。直到有一天,某位客户在别人的截图中看到了自己的数据。
会员专享
余下内容仅对会员开放。 会员可读完整内容 —— 每一个公开发布的观点背后,那些框架、决策与推理的全貌。
— 完整文章,包含未公开存档的部分 — 可落地的工作框架,附带权衡与决策依据 — 新文章抢先看,先于公开发布 登录以继续阅读→ 随时取消 · 一次订阅,畅读全部