Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

动态、身份感知、安全的 Sandbox auth

原文:Dynamic, identity-aware, and secure Sandbox auth / 2026-04-13 Source: https://blog.cloudflare.com/sandbox-auth/

随着 AI 大语言模型 以及 OpenCode、Claude Code 这类脚手架变得越来越强大,我们看到越来越多用户在 chat 消息、Kanban 更新、vibe coding UI、终端会话、GitHub 评论等场景中拉起沙箱化的 agent。

Sandbox 是超越简单容器的重要一步,因为它给你几样东西:

  • 安全:任何不可信的终端用户(或失控的 LLM)都可以在 sandbox 中运行,不会危及主机或并行运行的其他 sandbox。这传统上(但 并不总是)由 microVM 实现。

  • 速度:终端用户应该能够快速取到一个新 sandbox 并且 快速恢复一个之前用过的状态。

  • 控制:可信 的平台需要能在 sandbox 这一 不可信 域内采取行动。这可能意味着把文件挂载到 sandbox、控制哪些请求能访问它,或执行特定命令。

今天,我们激动地为 Sandboxes 和所有 Containers 添加另一个关键控制组件:outbound Workers。这些是可编程的出口代理,让运行 sandbox 的用户能轻松连接不同服务、添加 可观测性,以及——对 agent 尤为重要——添加灵活且安全的认证。

它如何工作

下面快速看看用一个 outbound Worker 给 header 加一个密钥:

class OpenCodeInABox extends Sandbox {
  static outboundByHost = {
    "github.com": (request, env, ctx) => {
      const headersWithAuth = new Headers(request.headers);
      headersWithAuth.set("x-auth-token", env.SECRET);
      return fetch(request, { headers: headersWithAuth });
    }
  }
}

任何时候 sandbox 内的代码向 "github.com" 发起请求,该请求都会被代理到 handler。这让你可以对每个请求做任何事,包括日志、修改或取消。本例中,我们安全地注入了一个密钥(下文细说)。代理跑在与 sandbox 相同的机器上,可访问分布式状态,并能用简单 JavaScript 轻松修改。

我们对它给 Sandboxes 带来的全部可能性感到兴奋,尤其是围绕 agent 认证的能力。在进入细节之前,我们先回顾传统的认证形式,以及为何我们认为有更好的方案。

agentic 工作负载常见的认证

agentic auth 的核心问题是我们不能完全信任工作负载。虽然我们的 LLM 不是恶意的(至少现在还不是),我们仍然需要保护措施,确保它们不会不当使用数据或执行不该做的操作。

有几种常见方法为 agent 提供认证,各有缺点:

标准 API token 是最基础的认证方法,通常通过环境变量或挂载的 secret 文件注入到应用中。这可能是最简单的方法,也是最不安全的。你必须信任 sandbox 不会以某种方式被攻陷,或在请求时意外外泄 token。既然不能完全信任 agent,你需要设置 token 过期与轮换,这可能很麻烦。

工作负载身份 token(如 OIDC token)可以解决其中一些痛点。你不再授予 agent 一个具有通用权限的 token,而是给它一个证明其身份的 token。这样,agent 不再直接持有某个服务的访问 token,而是可以用一个身份 token 换取一个非常短期的访问 token。OIDC token 可以在某个 agent 工作流完成后被作废,过期更易管理。工作负载身份 token 的最大缺点之一是集成可能不灵活。许多服务不原生支持 OIDC,因此为了与上游服务可用集成,平台需要自行打造换 token 服务。这让采用变得困难。

自定义代理 提供最大灵活性,可以与工作负载身份 token 配合。如果你能让一些或全部 sandbox 出口流量穿过一段可信代码,你就能插入任何你需要的规则。也许 agent 通信的上游服务 RBAC 不好,无法提供细粒度权限。没问题,你自己写控制和权限!这对需要用细粒度控制锁死的 agent 是个好选择。但是,如何拦截 sandbox 的全部流量?如何搭一个动态、易编程的代理?如何高效地代理流量?这些都不是容易解的问题。

带着这些不完美的方法,理想的认证机制是什么样?

理想情况下,它是:

  • 零信任。 任何 token 都不会在任何时间被授予不可信用户。

  • 简单。 易于编写。不涉及一套复杂的铸造、轮换和解密 token 系统。

  • 灵活。 我们不依赖上游系统提供我们需要的细粒度访问。我们可以应用任何规则。

  • 身份感知。 我们能识别发起调用的 sandbox,并对其应用特定规则。

  • 可观测。 我们能轻易收集关于哪些调用正在发生的信息。

  • 高性能。 我们不在中央或慢速来源做来回。

  • 透明。 沙箱化的工作负载不必感知到它。一切照常。

  • 动态。 我们能动态改变规则。

我们相信 outbound Workers for Sandboxes 在所有这些方面都符合要求。看看怎么样。

outbound Workers 实战

基础:限制与可观测性

首先看一个非常基础的例子:记录请求并拒绝特定操作。

我们在这里使用 outbound 函数,它会拦截 sandbox 发出的所有 HTTP 请求。用几行 JavaScript,就能确保只允许 GET,并日志并拒绝任何不允许的方法。

class MySandboxedApp extends Sandbox {
  static outbound = (req, env, ctx) => {
    // Deny any non-GET action and log
    if (req.method !== 'GET') {
      console.log(`Container making ${req.method} request to: ${req.url}`);
      return new Response('Not Allowed', { status: 405, statusText: 'Method Not Allowed'});
    }

    // Proceed if it is a GET request
    return fetch(req);
  };
}

这个代理跑在 Workers 上,与 sandbox VM 在同一机器。Workers 是为快速响应而设计的,常常坐在缓存的 CDN 流量前,所以额外延迟极小。

因为它跑在 Workers 上,我们获得开箱即用的可观测性。你可以 在 Workers dashboard 查看日志和出站请求,或 导出它们 到你选择的应用性能监控工具。

零信任凭据注入

我们如何用它为我们的 agent 强制执行 零信任环境?设想我们想向一个私有 GitHub 实例发起请求,但绝不希望我们的 LLM 接触到私有 token。

我们可以用 outboundByHost 为特定域名或 IP 定义函数。本例中,如果域名是 “my-internal-vcs.dev”,我们注入一个受保护的凭据。沙箱化的 agent 从不接触 这些凭据。

class OpenCodeInABox extends Sandbox {
  static outboundByHost = {
    "my-internal-vcs.dev": (request, env, ctx) => {
      const headersWithAuth = new Headers(request.headers);
      headersWithAuth.set("x-auth-token", env.SECRET);
      return fetch(request, { headers: headersWithAuth });
    }
  }
}

也很容易基于容器身份对响应做条件化。你不必为每个 sandbox 实例注入相同 token。

 static outboundByHost = {
  "my-internal-vcs.dev": (request, env, ctx) => {
    // note: KV is encrypted at rest and in transit
    const authKey = await env.KEYS.get(ctx.containerId);

    const requestWithAuth = new Request(request);
    requestWithAuth.headers.set("x-auth-token", authKey);
    return fetch(requestWithAuth);
  }
}

使用 Cloudflare 开发者平台

正如你在上一例可能注意到的,使用 outbound Workers 的另一大优势是它让与 Workers 生态的集成更容易。以前,如果用户想访问 R2,他们必须注入 R2 凭据,然后从容器调用公共 R2 API。KVAgents、其他 Containers、其他 Worker 服务等等 都是一样。

现在,你只需在 outbound Workers 中调用 任意 binding

class MySandboxedApp extends Sandbox {
  static outboundByHost = {
    "my.kv": async (req, env, ctx) => {
      const key = keyFromReq(req);
      const myResult = await env.KV.get(key);
      return new Response(myResult);
    },
    "objects.cf": async (req, env, ctx) => {
      const prefix = ctx.containerId
      const path = pathFromRequest(req);
      const object = await env.R2.get(`${prefix}/${path}`);
      const myResult = await env.KV.get(key);
      return new Response(myResult);
    },
  };
}

不必解析 token、设置策略,我们可以用代码和任何逻辑轻松条件化访问。在 R2 例子中,我们也能用 sandbox 的 ID 进一步范围化访问。

让控制变成动态

网络控制也应该是动态的。在许多平台,Container 与 VM 网络的 config 是静态的,大致这样:

{
  defaultEgress: "block",
  allowedDomains: ["github.com", "npmjs.org"]
}

这比什么都没有好,但我们能做得更好。对许多 sandbox,我们可能希望启动时应用一种策略,在执行特定操作后再用另一种覆盖。

例如,我们可以启动一个 sandbox,通过 NPM 和 Github 抓取依赖,然后锁死出口。这确保我们尽可能短时间地打开网络。

为此,我们可以使用 outboundHandlers,它让我们定义可以通过 setOutboundHandler 方法以编程方式应用的任意出站 handler。每个也接收参数,让你可以用代码自定义行为。本例中,我们用自定义的 “allowHosts” 策略允许某些主机名,然后关闭 HTTP。

class MySandboxedApp extends Sandbox {
  static outboundHandlers = {
    async allowHosts(req, env, { params }) {
     const url = new URL(request.url);
     const allowedHostname = params.allowedHostnames.includes(url.hostname);

      if (allowedHostname) {
        return await fetch(newRequest);
      } else {
        return new Response(null, { status: 403, statusText: "Forbidden" });
      }
    }

    async noHttp(req) {
      return new Response(null, { status: 403, statusText: "Forbidden" });
    }
  }
}

async setUpSandboxes(req, env) {
  const sandbox = await env.SANDBOX.getByName(userId);
  await sandbox.setOutboundHandler("allowHosts", {
    allowedHostnames: ["github.com", "npmjs.org"]
  });
  await sandbox.gitClone(userRepoURL)
  await sandbox.exec("npm install")
  await sandbox.setOutboundHandler("noHttp");
}

这还可以更进一步。你的 agent 可能根据当下需要的工具问终端用户“你想允许向 cloudflare.com 的 POST 请求吗?“。借助动态 outbound Workers,你可以即时修改 sandbox 规则以提供这种级别的控制。

用 MITM 代理实现 TLS 支持

要对请求做超出允许或拒绝之外的有用事情,你需要能访问内容。这意味着如果你在做 HTTPS 请求,它们需要被 Workers 代理解密。

为实现这点,每个 Sandbox 实例都创建一个独特的临时证书颁发机构(CA)和私钥,并把 CA 放进 sandbox。默认情况下,sandbox 实例会信任这个 CA;标准容器实例可以选择信任它,例如调用 sudo update-ca-certificates

export class MyContainer extends Container {
  interceptHttps = true;
}

MyContainer.outbound = (req, env, ctx) => {
  // All HTTP(S) requests will trigger this hook.
  return fetch(req);
};

TLS 流量由 Cloudflare 的隔离网络进程通过执行 TLS 握手来代理。它从一个临时且独特的私钥生成一个叶 CA,并使用从 ClientHello 提取的 SNI。然后它在同一台机器上调用配置的 Worker 来处理 HTTPS 请求。

我们的临时私钥和 CA 永远不会离开容器运行时 sidecar 进程,且不会与其他容器 sidecar 进程共享。

有了这些,outbound Workers 就像一个真正透明的代理。Sandbox 不需要感知任何特定协议或域名——所有 HTTP 与 HTTPS 流量都会通过 outbound handler 进行过滤或修改。

引擎盖下

为了在 ContainerSandbox 中实现以上功能,我们给 ctx.container 对象添加了新方法:interceptOutboundHttpinterceptOutboundHttps,它们在特定主机名(带基本 glob 匹配)、IP 范围拦截出站请求,并可用于拦截所有出站请求。这些方法用一个 WorkerEntrypoint 调用,它将作为 outbound Worker 的入口被设置好。

export class MyWorker extends WorkerEntrypoint {
 fetch() {
   return new Response(this.ctx.props.message);
 }
}

// ... inside your container DurableObject ...
this.ctx.container.start({ enableInternet: false });
const outboundWorker = this.ctx.exports.MyWorker({ props: { message: 'hello' } });
await this.ctx.container.interceptOutboundHttp('15.0.0.1:80', outboundWorker);

// From now on, all HTTP requests to 15.0.0.1:80 return "hello"
await this.waitForContainerToBeHealthy();

// You can decide to return another message now...
const secondOutboundWorker = this.ctx.exports.MyWorker({ props: { message: 'switcheroo' } });
await this.ctx.container.interceptOutboundHttp('15.0.0.1:80', secondOutboundWorker);
// all HTTP requests to 15.0.0.1 now show "switcheroo", even on connections that were
// open before this interceptOutboundHttp

// You can even set hostnames, CIDRs, for both IPv4 and IPv6
await this.ctx.container.interceptOutboundHttp('example.com', secondOutboundWorker);
await this.ctx.container.interceptOutboundHttp('*.example.com', secondOutboundWorker);
await this.ctx.container.interceptOutboundHttp('123.123.123.123/23', secoundOutboundWorker);

所有到 Workers 的代理都在跑 sandbox VM 的同一机器本地完成。即使容器与 Worker 之间通信是“无认证“的,它也是安全的。

这些方法可以在任何时候调用,容器启动前或后,即使连接仍然打开。发送多个 HTTP 请求的连接会自动接收新 entrypoint,所以更新 outbound Workers 不会断开现有 TCP 连接或中断 HTTP 请求。

本地开发用 wrangler dev 也支持出口拦截。为实现这点,我们在本地容器的网络命名空间内自动起一个 sidecar 进程。我们把这个 sidecar 组件叫做 proxy-everything。一旦 proxy-everything 附加上,它会应用相应的 TPROXY nftable 规则,将本地 Container 中匹配的流量路由到 workerd——Cloudflare 的开源 JavaScript 运行时,它运行 outbound Worker。这让本地开发体验镜像生产环境,所以测试与开发都保持简单。

试试 outbound Workers

如果你还没试过 Cloudflare Sandboxes,看看 入门指南。如果你已经是 ContainersSandboxes 用户,通过 阅读文档 并升级到最新版的 @cloudflare/containers@cloudflare/sandbox,即可开始使用 outbound Workers。

– 原文译于 2026-04-30