Cloudflare 说他们做了个 OS,拆开发现是一套 Agent 安全沙箱

发布于 · 3,245 字 · 约 8 分钟#Github 解读#Agent 基建原文链接
Cloudflare 说他们做了个 OS,拆开发现是一套 Agent 安全沙箱 封面图
  • Cloudflare OS 提出 Gatekeeper 权限管理,实现 Agent 先模拟执行后用户异步审批
  • 能力基于引入而非授权,Agent 默认无权,需显式引入资源,类似手机应用权限模型
  • Gadget 为每个用户创建独立沙箱实例,安全隔离且可随时由 Agent 修改代码
  • 异步审批模式包含审批队列、模拟层和单飞 drain,保证安全且不打断 Agent 自主性
  • 代码中 AutoApprovalDrainer 的 130 行实现是核心可复用组件,解决并发审批顺序问题

你给 Agent 派了个任务:「去 GitHub 上把 issue #42 的标题改了,再加个 label。」

Agent 启动,调用工具,一切顺利。然后它停住了。

「这个操作需要你的审批,请确认。」

你正在回另一个消息,顺手点了确认。Agent 继续,5 秒后又停了。「修改标题需要你的审批,请确认。」你又点了一下。然后它又停了。「添加 label 需要你的审批,请确认。」

回头一看,Agent 像个刚学会走路的小孩,每一步都要你扶。你走开去喝杯咖啡,回来发现它还在第一步等着。

说真的,用过 Agent 的应该都经历过这个场景。传统的人机交互审批(Human-in-the-loop)是同步的,Agent 要停下来,等你说「行」,才能继续。这带来的直接后果是,你受不了,然后把所有审批都设成 auto-approve,或者干脆 --dangerously-skip-permissions,眼不见心不烦。但显然,这不安全。

Cloudflare 最近开源了一个项目,叫 Cloudflare OS。名字听起来像个操作系统,但实际上它是一个「AI 生产力环境」。放在 3.3K Star 的数字旁边,它的核心创新不是又一个聊天框加模型切换器,而是一套叫 Gatekeeper 的权限管理机制,把上面那个烦人的问题彻底翻了个面。

为什么叫 OS

坦白讲,第一眼看到「Cloudflare OS」这个项目名,我以为是 Cloudflare 做了一个桌面操作系统。点进去发现不是,但 README 里花了整整一张表来解释它为什么配叫 OS:

传统 OSCloudflare OS
kernelpackages/workshop-backend
device driverspackages/gatekeeper-*
shellpackages/workshop-frontend
processesgadgets
executablesblueprints
usersusers
ACLsshared permissions

这个类比不是营销噱头。它的「内核」workshop-backend 确实在做类似 OS 内核的事情:连接用户到程序和设备(Gadget 和 Gatekeeper),通过沙箱隔离应用,强制访问控制。Gatekeeper 像是设备驱动,把第三方服务(GitHub、Google Docs、Slack)抽象成一组标准化的 API,供 Agent 和 Gadget 调用。

但我觉得最妙的是它表格里最后一行:「??? | agents」。传统 OS 没有 Agent 的概念,而 Cloudflare OS 认为 AI Agent 不应该被当作普通用户来对待。它们需要有自己的权限边界,同时对人类用户负责。在操作系统层面给 Agent 做「一等公民」支持,目前还没有哪个 OS 做到。

不过话说回来,如果你把它当成一个正经的操作系统来用,可能会失望。它其实是一个 运行在 Workers 上的 AI 工作台,你能用它来聊天、写代码、做幻灯片、搭小应用,跟 Google Docs 差不多,但每个「文档」本质上都是一个独立的沙箱化应用。

最烦人的一步:同步审批

回到开头那个场景。

Agent 要修改 GitHub issue,需要你的审批。问题是,审批是同步的,Agent 必须停下来等。这意味着你没法把任务丢给 Agent 然后走开,你得像奶孩子一样守在旁边。

Cloudflare OS 的 Gatekeeper 解决这个问题的思路很直接:让 Agent 先跑完,你再来审批。

具体来说,当 Agent 要执行一个需要审批的操作时,Gatekeeper 不会阻塞它,而是执行一个「模拟」版本。结果看起来跟真的执行了一样,Agent 可以继续往下走,读模拟出来的结果,然后做后续决策。背后的 StreamingCursor 类(在 packages/gatekeeper-github/src/github.ts 里)通过 #overlay 方法把模拟结果注入到数据流中,让 Agent 以为操作已经执行了。

等 Agent 跑完整个任务,把所有操作都列出来,你再一次性审批,或者逐条审批,看你自己。但关键是,审批发生在你方便的时候,而不是 Agent 卡住的时候。

这个机制在代码里体现得很清楚。packages/workshop-backend/src/auto-approval.ts 里有个 AutoApprovalDrainer 类,负责按顺序处理排队中的审批动作。它的核心逻辑是一个单飞并发控制(per-gatekeeper single-flight guard):同一个 Gatekeeper 的审批队列不会同时跑两个 drain 循环,但如果 drain 过程中又有新的操作进来,它会记一个「rerun」标记,当前循环结束后再跑一轮。

async drain(gatekeeperId: number): Promise<void> {
  if (this.#draining.has(gatekeeperId)) {
    this.#draining.set(gatekeeperId, true);  // 标记需要重跑
    return;
  }
  this.#draining.set(gatekeeperId, false);
  try {
    do {
      this.#draining.set(gatekeeperId, false);
      await this.#drainOnce(gatekeeperId);
    } while (this.#draining.get(gatekeeperId));
  } finally {
    this.#draining.delete(gatekeeperId);
  }
}

这个模式我之前在别的项目里没见过。它解决了一个很实际的并发问题:Agent 可能在 drain 过程中提交新的操作,如果没有 rerun 标记,新提交的操作可能要等到下一次触发才能被审批。但有了这个循环,只要 drain 过程中有新操作进来,就会自动再跑一轮,直到队列清空。

更妙的是,它不会跳过人工审批门。#drainOnce 方法的逻辑是:按顺序扫描待审批操作,一旦遇到一个需要人工审批的(autoApprovable 不是 true 或没有对应的规则),就停下来,不跳过它去审批后面的。这保证了审批顺序的严格性,你不会因为设了自动审批而让后面的操作先被执行。

能力不是授权的,是引入的

说完了审批,再来看看整个权限模型的设计。

用过 MCP 的都知道,配置 MCP 服务器的时候,你需要把 API key、token 这些东西写进配置文件。配置完之后,Agent 在任何对话里都可以随意调用这些工具。这叫做「ambient 权限」,权限是环境的一部分,Agent 天生就有。

Cloudflare OS 的做法完全不同。每个 Agent 和每个 Gadget,默认情况下什么都没有。即使你给整个系统配好了 GitHub 账号,Agent 也不能自动访问你的仓库。你必须显式地「引入」(introduce)一个资源给 Agent,比如粘贴一个 GitHub 仓库链接,或者通过 UI 选择。

这就是 capability-based 安全模型的核心思想:权限不是一次配置永久有效的,而是每次使用时按需授予的。Agent 可以主动请求某个资源,然后你来决定给不给。这跟手机 App 的权限模型很像,App 需要访问相册的时候,系统弹窗问你要不要授权,而不是装 App 的时候就全给了。

这个设计在 packages/workshop-shared/src/gatekeeper.ts 的接口定义里体现得很清楚。每个 Gatekeeper 实现了 ApprovalQueue 接口,所有操作都经过审批队列,而不是直接执行。代码里 400 多行的接口定义,描述了一个完整的资源生命周期:连接、授权、操作、审计。

Gadget:每个用户都有自己的一份 App

再来聊聊 Gadget 这个设计。它可能是 Cloudflare OS 里最反直觉的部分。

传统的 SaaS 架构是多租户的,一个 App 运行在服务器上,所有用户连接到同一个实例。你在 Google Docs 里编辑文档,背后是 Google 的服务器在处理所有用户的请求。

Cloudflare OS 的 Gadget 完全不同。当你让 Agent 做一个幻灯片 App 时,系统会为你一个人创建一个独立的实例。这个实例运行在一个单独的 Dynamic Worker Facet 里,跟其他所有人的幻灯片 App 完全隔离。

这意味着两件事:

第一,安全。Gadget 的服务器端运行在 Dynamic Worker 中,网络访问被完全禁用,它只能通过 Workers Bindings 跟特定的外部资源通信。客户端代码跑在沙箱 iframe 里,通过 postMessage() 跟父窗口通信,CSP 和 iframe sandbox 属性把网络访问也封死了。packages/workshop-backend/src/overseer.ts 里的 CODE_MODE_HARNESS 定义了 Agent 代码的执行环境,每个 Agent 脚本实际上是一个完整的 Worker entrypoint,有独立的 verify() 和 run() 方法。

第二,可修改。因为这个 App 是你私有的,你随时可以让 Agent 改它的代码。缺个功能?跟 Agent 说一声就行。不用提 feature request,不用等开发者排期。在 AI 时代,当每个用户都能让 Agent 改代码的时候,集中式 SaaS 的「等更新」模式确实不太合理了。

Gadget 的客户端和服务器通过 Cap‘n Web RPC 通信。这个协议是 Cloudflare 自己搞的,特点是低样板代码,你只需要在服务器端定义个方法,客户端就能像调本地函数一样调用它。而且由于 Cap’n Web 的接口定义清晰,Agent 可以直接调用这些 API,不需要额外写 MCP 服务器。

还有什么

Cloudflare OS 虽然是 v2 重写版,但功能上已经比较完整了。我快速列一下核心能力:

  • 多模型支持:通过 Pi 框架的 pi-agent-core 接入几乎所有主流 LLM 提供商,也支持自托管模型
  • 实时多人协作:每个 Gadget 背后是 Durable Object,实时同步开箱即用,Agent 默认就做了
  • Blueprint 共享:你可以把 Gadget 的代码打包成 Blueprint 分享给别人,对方可以创建自己的副本
  • Gatekeeper 生态:目前内置了 GitHub、Google、Slack、Notion、Confluence、Spotify 等 11 个 Gatekeeper
  • Agent 技能系统:支持自定义 .agents/skills/ 目录,跟 Claude Code 的 AGENTS.md 类似
  • AI Gateway 计费:内置了 AI Gateway 的用量追踪和计费限制

仓库里藏着什么

说完了亮点,来聊聊我从源码里读出的一些东西。

第一个发现是:README 里有个未经证实的性能声明。它说「Cloudflare OS 的 coding agent 通常比通用 coding agent 表现更好、更快、用更少的 token」。这个说法可能成立,毕竟 Gadget 的环境简化了,Agent 不需要处理复杂的上下文,但整个仓库里找不到任何 benchmark 数据。没有 n=多少的测试,没有对比基线,没有 token 消耗的统计数据。作为读者,我倾向于相信这个说法有道理,但「有道理」和「有证据」是两回事。

第二个是 bus factor 问题。这个项目 3.3K Star,但只有 12 个贡献者。看了一下代码提交历史,大部分来自 Cloudflare Workers 团队的内部成员。README 也明确说了「目前不接受外部贡献」,理由是「AI 让写代码变得容易了,难的是 review 和保持产品质量」。这个说法其实挺坦诚的,但也意味着项目的长期维护完全依赖 Cloudflare 内部团队。如果公司战略调整,这个项目可能说停就停。

第三个是 OAuth token 刷新问题。Issue #10 暴露了一个实际的问题:GitHub 的 OAuth 凭证过期后,Gatekeeper 无法通过重新授权恢复连接。这意味着如果你配好了 GitHub 集成,token 过期后需要手动重新配置。对于一个宣称「让非技术用户也能安全使用」的平台来说,这个体验还有改进空间。

第四个是 外部贡献不开放。这在一个开源项目里有点反直觉。但换个角度想,Cloudflare OS 的定位是「你可以 Fork 它,然后改成你公司的版本」,而不是「大家一起改进这个项目」。它是一个分发的起点,而不是协作的中心。这个定位在 README 里说得很清楚,没有什么误导。

异步审批能带走什么

拆完 Cloudflare OS,我最想带走的是那套异步审批模式。

传统的人机交互审批是同步的,同步意味着 Agent 的自主性被打断。Cloudflare OS 的 Gatekeeper 用「模拟执行 + 延迟审批」的方式,既保证了安全,又不牺牲 Agent 的自主性。这个模式可以迁移到任何 Agent 系统里,不管你是用 Claude Code、OpenAI Codex 还是自己搭的 Agent 框架。

具体来说,如果你想在自己的 Agent 系统里实现类似机制,核心是三个组件:

  1. 审批队列:所有对外部资源的操作都先进入队列,而不是直接执行
  2. 模拟层:在队列等待审批期间,给 Agent 返回模拟结果,让它能继续执行
  3. 单飞 drain:确保审批顺序正确,不跳过人工审批门,同时处理并发提交

这三个组件在 Cloudflare OS 的代码里都有可参考的实现。AutoApprovalDrainer 的 130 行代码,可以说是我在整个仓库里看到的最有「带走价值」的部分。

当然,这套机制也有它的前提,你需要一个可控的执行环境,才能做模拟执行。Cloudflare OS 能做到是因为所有 Gadget 都跑在 Workers Runtime 上,模拟的副作用可以被精确控制。如果你的 Agent 操作的是物理设备或不可逆的外部系统,模拟的可靠性就要打个问号。

Cloudflare OS 还在早期开发阶段,v2 重写刚刚完成,很多边角还没磨平。但它对「Agent 应该如何安全地访问外部资源」这个问题的回答,比市面上大多数 Agent 框架都认真。如果你也在做 Agent 基建,值得去看看它的 Gatekeeper 设计,特别是那 130 行的 AutoApprovalDrainer。

评论互动

© 2026 王若风的技术博客 · Powered by Astro