我们如何构建安全、可扩展的代理沙箱基础设施(8分钟阅读)

TLDR AI 新闻

摘要

Browser Use 描述了隔离执行代码的 AI 代理的两种模式:隔离工具与隔离代理。他们使用 AWS 上的 Unikraft 微虚拟机实现了代理隔离模式,获得了安全、可扩展且一次性的沙箱。

有两种方法可以对能够执行代码的代理进行沙箱处理:隔离工具或隔离代理。代理应该没有任何值得窃取的东西,也没有任何值得保留的东西。隔离代理需要在每次操作中增加一次网络跳转,并部署更多服务,但不存在可窃取的秘密,无需保留状态,并且代理可以独立地终止、重启和扩展。
查看原文
查看缓存全文

缓存时间: 2026/05/16 00:14

有两种方式可以为能够执行代码的代理搭建沙箱:隔离工具或隔离代理。代理本身应无有价值信息可窃取,也无状态需要保留。隔离代理需要在每次操作中增加一次网络跳转并部署更多服务,但代理不包含任何机密信息、无需保留状态,可以独立终止、重启和弹性伸缩。


我们如何构建安全、可扩展的代理沙箱基础设施

从 AWS Lambda 到 Unikraft 微虚拟机,配合控制平面架构。

我们的历程

我们在 Browser Use 运行着数百万个网页代理。一开始,我们使用 AWS Lambda 上的纯浏览器代理,每次调用完全隔离,弹性伸缩即时生效,无需担心任何机密信息。

后来我们加入了代码执行功能。代理可以编写并运行 Python 代码、执行 Shell 命令、创建文件。我们将此功能构建为一个隔离沙箱,代理将其作为工具调用。安全性没有问题:代码运行在沙箱内,而非后端。

但代理循环仍然与我们的 REST API 运行在同一后端。需要重部署?所有运行中的代理都会终止。遇到内存密集型代理?API 响应变慢。两种根本不同的工作负载挤在同一个进程里。

两种模式

当代理可以运行任意代码时,它能访问机器上的任何内容:环境变量、API 密钥、数据库凭据、内部服务。它必须与你的基础设施和机密信息隔离。有两种方式实现这一点。

模式1:隔离工具。 代理运行在你的基础设施上。危险操作(代码执行、终端访问)运行在单独的沙箱中。代理通过 HTTP 调用沙箱。代码运行在无信息可泄露的地方。

模式1 - 隔离工具。

模式1 - 隔离工具。

模式2:隔离代理。 整个代理运行在一个沙箱中,不包含任何机密信息。它通过一个持有所有凭据的控制平面与外界通信。

代理变为可丢弃式。无机密可窃取,无状态需要保留,你可以独立地终止、重启和弹性伸缩它。控制平面持有所有真相。

模式2 - 隔离代理。

模式2 - 隔离代理。

我们从模式1开始,后来迁移到了模式2。

沙箱

同一个容器镜像在所有环境中运行。在生产环境中,它以 Unikraft 微虚拟机形式运行;在本地开发和评估中,它以 Docker 容器形式运行。一个简单的配置开关(sandbox_mode: 'docker' | 'ukc')控制供应代码走哪条路径。

生产环境下的 Unikraft

每个代理拥有自己的 Unikraft 微虚拟机,启动时间不到一秒。我们通过 Unikraft Cloud 的 REST API 在 AWS 专用裸金属服务器上进行供应。

沙箱从外部仅接收三个环境变量:SESSION_TOKENCONTROL_PLANE_URLSESSION_ID。没有 AWS 密钥、没有数据库凭据、没有 API 令牌。

Unikraft 为我们提供了开箱即用的缩至零能力。当沙箱空闲时,虚拟机挂起;当后续请求到来时,它恢复执行。处于查询间隙的沙箱几乎不花费任何成本,但能立即唤醒以处理后续任务。

我们将沙箱分布在多个 Unikraft 数据中心,防止单个数据中心成为瓶颈。

开发与评估环境下的 Docker

在本地和评估流水线中,同一镜像作为 Docker 容器运行。相同的镜像、相同的入口点、相同的控制平面协议。我们可以在开发笔记本上运行完全相同的代理,并行启动数百个进行评估,然后部署到 Unikraft 用于生产环境。

加固措施

沙箱在运行任何代理代码之前会执行以下几个步骤:

1. 仅字节码执行。 在 Docker 构建过程中,我们将所有 Python 源代码编译为 .pyc 字节码,然后删除所有 .py 文件。代理框架代码以 root 身份加载到内存中。加载完成后,源代码消失。

2. 权限降级。 入口点以 root 身份启动(需要读取 root 拥有的字节码),然后立即通过 setuid/setgid 降级为 sandbox 用户。此后,所有代码以非特权身份运行。

3. 环境变量清除。 在将 SESSION_TOKENCONTROL_PLANE_URLSESSION_ID 读取到 Python 变量后,我们从 os.environ 中删除这些变量。如果代理检查环境变量,这些信息已经消失。该令牌在沙箱网络之外也毫无用处。虚拟机位于私有 VPC 中,除了与控制平面通信,没有任何其他权限。

控制平面的工作原理

可以把控制平面看作一个代理服务。沙箱无法直接访问外界。每个请求都必须经过控制平面。需要调用 LLM?通过控制平面。需要上传文件到 S3?通过控制平面。这是代理与其 VM 外部通信的唯一途径。

它是一个无状态的 FastAPI 服务。来自沙箱的每个请求都带有 Bearer: {session_token} 头。控制平面按令牌查找会话,验证其仍处于活动状态,然后使用真实凭据执行操作。

LLM 代理

对于每次 LLM 调用,沙箱只发送新的消息。控制平面在数据库中拥有完整的对话历史,在每次调用时重构它,并将完整上下文转发给提供商。这使沙箱保持无状态。你可以终止它并启动一个新的,对话可以从上次中断的地方继续。

控制平面还强制执行成本上限和处理计费。沙箱只专注于任务本身。

通过预签名 URL 进行文件同步

沙箱有一个 /workspace 目录,代理在其中读写文件。一个文件同步服务监视更改并定期将它们同步到 S3,但沙箱从不会看到 AWS 凭据。相反,它向控制平面请求预签名 URL:

  • 沙箱检测到 /workspace 中的文件变更
  • 沙箱调用 POST /presigned-urls 并附带文件路径
  • 控制平面生成预签名 S3 上传 URL(作用域限定于该会话)
  • 沙箱使用这些 URL 直接将文件上传到 S3

下载操作以相反的方式工作。沙箱获得直接的、作用域限定的 S3 访问权限,而无需持有任何 AWS 凭据。

网关协议

在沙箱内部,代理通过一个“网关”协议与控制平面通信:

class AgentGateway(Protocol):
    async def invoke_llm(self, new_messages, tools, tool_choice) -> LLMResponse: ...
    async def persist_messages(self, messages) -> None: ...

在生产环境中,ControlPlaneGateway 向控制平面发送 HTTP 请求。对于本地开发和评估,DirectGateway 直接调用 LLM 并在内存中维护历史记录。代理代码无需知道它正在使用哪一个。相同的接口、相同的行为、不同的后端。

弹性伸缩

控制平面是无状态的:验证令牌、执行操作、返回结果。需要更多代理?启动更多沙箱。需要更高吞吐量?在负载均衡器后面添加控制平面实例。每一层都根据自身瓶颈独立伸缩。

我们的后端运行在 ECS Fargate 上,位于私有子网中,前面有 ALB 负载均衡器。控制平面根据 CPU 利用率自动伸缩。沙箱通过 Unikraft 独立伸缩。每个会话拥有自己的虚拟机,Unikraft 负责跨数据中心调度。

独立伸缩服务(后端、代理、控制平面)

总结

有两种方式可以为能够执行代码的代理搭建沙箱。你可以隔离工具(将代码执行放在沙箱中,代理保持在后端),也可以隔离代理(将整个代理放在沙箱中,通过控制平面与外界通信)。

我们选择了模式2。控制平面持有所有凭据,并作为所有操作的代理:LLM 调用、文件存储、计费。沙箱只接收三个环境变量,无法访问其他任何内容。它在生产环境中作为 Unikraft 微虚拟机运行,在开发和评估中作为 Docker 容器运行。同一镜像,随处可用。

这样做的代价是每次操作多一次网络跳转,并且需要部署三个服务而非一个。在实际应用中,与 LLM 响应时间相比,这种延迟几乎可以忽略不计;而运维复杂性正是运维团队已经擅于处理的类型。

关键要点:你的代理应该既无有价值信息可窃取,也无状态需要保留。

相似文章

代理环境的安全与维护

Reddit r/openclaw

一位开发者构建了 Terrarium,这是一个开源沙箱解决方案,用于安全运行多个AI代理,提供隔离世界、反向代理管理和状态回滚功能。