隆重推出 Kitesurf:一款以智能体为先的浏览器,运行在 Cloudflare Workers 上的 V8 隔离环境中
我们应该构建自己的浏览器吗?
这是多年来在 Cloudflare 内部每隔几个月就会被提起的那类问题之一。不出所料,这类问题会引发长长的讨论串,里面充斥着各种理由和极具说服力的论据,说明我们为什么应该去做。浏览器显然是我们每天在电脑上使用的最重要的软件;它可以说是互联网的操作系统。我们是一家以帮助构建更好的互联网为使命的公司——谁不想接下构建一款新浏览器的挑战呢?
但我们始终未能在这项事业的技术难度与这样做所能解决的独特问题之间找到恰当的平衡。于是,这个想法一次又一次被搁置。直到现在。
奇妙的事情发生了:我们到达了一个临界点,我们的开发者平台中一系列强大的技术进步成为现实,与此同时,AI 智能体的出现以及对一种新型浏览器的需求也变得至关重要。
在 Workers 中运行 WebAssembly(Wasm) 如今已经非常成熟。诸如 动态 worker、基于 SQLite 的 Durable Objects、Worker 到 Worker 的 RPC、服务绑定、更高的 NodeJS 兼容性 以及更高的 限制 等基础能力,为远比以往更具野心、更复杂的应用打开了大门,而这些应用在过去根本不可能实现。
Browser Run 是我们的无头浏览器自动化 API 产品,随着 AI 的兴起,它迎来了巨大的增长。AI 智能体需要浏览器来执行许多任务,在很多情况下,没有浏览器就无法成功完成任务。
但问题在于——像 Chromium 这样的浏览器引擎是为人类而非智能体构建的,它们带来的额外开销是 AI 模型根本不需要的。它们消耗大量内存和算力,以至于为每个智能体都提供独立实例的成本高得令人望而却步,这导致 Web 的大部分内容只能被那些拥有更高参数化知识、最精密也最昂贵的 AI 模型所触及,而许多其他智能体应用则被拒之门外。
我们应该为 所有 智能体提供一个浏览器,它在对 AI 模型重要的方面表现出色,即便这意味着在对人类才有用的方面保持轻量。例如:
- AI 并不关心标签页、主题、浏览器扩展或跨设备同步。它关心的是 token 数量、上下文窗口、可扩展性、性能和成本。
- 结构化、机器可读的内容很重要,但视觉上的完美、流畅的 60-fps 滚动并不重要。即使 CSS 解析稍有偏差,或者渲染不是像素级完美,AI 智能体也照样能正常工作。
- 在 AI 使用浏览器这一场景下,威胁模型是不同的。提示词注入和工具安全等新问题才是重中之重。
面对这些认识,12 周前我们再次提出了那个问题:我们应该构建自己的浏览器吗?这一次答案是一致的:应该!
今天我们宣布推出 Kitesurf,这是一款完全运行在 Workers 之上的新浏览器,我们专为 AI 智能体打造,目前在 beta 阶段通过 Browser Run 免费提供。
在截图和 HTML 提取等常见智能体任务上,Kitesurf 的 CPU 和内存消耗显著低于 Chromium。接下来就是我们如何构建它的故事。系好安全带,内容会相当技术性——但我们保证会保持有趣。
缘起
Kitesurf 的起点和 Cloudflare 许多其他绝妙点子的起点一样。有人发现了某个有趣的东西,然后转眼间,他们就用一个看似不可能但极具吸引力的想法把团队其他人都“极客狙击”了。
我们的最初灵感来自 obscura,这是一个用 Rust 编写的无头引擎,面向 AI 自动化,具有“无 Chrome、无 Node.js、无依赖”的特点。

然后,在一个 AI 智能体的帮助下,我们尝试将其移植到 Workers。起初效果并不理想。但当我们给 AI 提供了一个扎实的计划和清晰的成功定义——详细到足以让智能体无限循环并在需要时提出问题——它确实奏效了。
被这个(勉强)能运行的概念验证震惊到了,我们决定让团队放手去做。
设计决策
以下是我们开始之前做出的一些设计决策。
测试,测试,测试
我们知道,从一个原型发展成一个真正能在生产环境中大规模用于任务的完整浏览器,需要大量的工作和迭代。我们不会隐瞒,使用 AI 来加速这一过程是关键。但如何在如此复杂的项目中使用 AI,在控制代码和结果质量的同时不损失速度?答案是尽可能多地提供测试。
于是有了 Web Platform Tests(WPT),这是理想的设置:一套广泛的成功标准,为 AI 智能体提供了清晰的目标来评估功能一致性。我们精心策划了分配给智能体的功能选择与顺序,让人类能够专注于架构工作和审查智能体的方案。
然而,WPT 测试的作用有限:它们衡量的是对 W3C 标准的符合程度,而非浏览器渲染真实网站并与之交互的能力。为弥合这一差距,我们结合了集成测试与视觉回归测试——它在真实网站上针对 Chromium 和 Kitesurf 运行多步骤 Puppeteer 测试,不仅比较其做出的断言,还在每一步渲染输出,以凸显任何不期望出现的差异。
尽可能使用 Rust
Cloudflare 长期以来一直致力于在 Workers 中为 WebAssembly(Wasm) 提供出色的支持。这很棒,因为我们可以使用高性能的 C、C++ 和 Rust 包,并将它们编译为 Wasm。如果我们使用 Emscripten(例如)及其众多层模拟依赖,编译出的二进制文件会变得臃肿且缓慢。
相反,我们选择尽可能使用原生 Rust,并使用 wasm-bindgen 直接编译为 WebAssembly,从而避免不必要的模拟层,尽可能贴近底层运行,可靠地。
异常处理
浏览器必须渲染整个不可靠、有时甚至充满敌意的网络,且绝不能丢弃它正在承载的页面,因此异常处理不仅仅关乎整洁——它是应用程序在遭遇不良输入时避免直接崩溃、得以存活下来的方式。
所以我们在最开始就承诺了一条规则:任何故障都降级为一个空白帧或缺失的元素,绝不导致会话中断。在每个边界处捕获故障,默认回退到安全且空的状态,并记录足够的信息以便诊断。
隔离
与在笔记本电脑上运行浏览器不同(在那种场景下,你访问的是自己信任的网站,在它们之间共享一些资源是可以接受的),AI 智能体被指向的是任务所要求的任何东西:来自任意来源的任意代码。
所以我们构建这个浏览器时,假设每一次页面加载都是不可信的输入,每一个会话都从全新状态开始。每个组件都是隔离的,只能访问其功能所严格必需的资源。

这似乎与 Cloudflare Workers 完美契合,其安全模型正是围绕设计上的隔离构建的。但该平台只为我们提供了隔离实例之间的边界。我们仍然必须在应用层面强制执行同样的原则,决定每个组件被允许接触什么,并确保没有任何东西泄漏到它不该跨越的页面。
尽可能无状态
状态正是让故障变得代价高昂的原因——如果没有什么需要重建,那么从崩溃中恢复不过是启动一个新实例并重放请求而已。无状态组件天生就是一次性的、可并行的:一旦卡住就立刻杀掉它,同时运行上千个,按需扩缩容而不是让它们一直保持热备。这完美契合自动化场景——负载以突发方式到来,而你能做的最省钱的事就是启动只消耗其所用资源、完成后即消失的工作。简而言之,凡是组件可以做到无状态的地方,就应该做到无状态。
我们是如何构建它的
有了良好的规划、广泛的测试和优秀的工具环境,我们准备好在最初的概念验证之外正式动手了。以下是 Kitesurf 中一个请求的宏观生命周期,这一流程至今仍然适用:

让我们深入了解让 Kitesurf 运转起来的三个主要组件:Engine、PageScript 和 PageRenderer。
从源站获取资源
为了渲染一个不受信任的网页,浏览器必须从互联网上获取任意资源——图片、字体、CSS、JavaScript 和 Wasm 文件。这是浏览器所能执行的最危险的操作之一。
Kitesurf 通过单一组件 SandboxOutbound worker 来完成这一操作,其他任何组件都不能直接访问网络——这由 Dynamic Workers 强制执行。Engine 用它来引导页面,获取主文档及其脚本,而 PageScript 则获取其他所有内容:样式表、图片、字体以及页面自身的 fetch() 调用。
我们使用 SandboxOutbound 来强制执行 CORS、注入浏览器形态的请求头、过滤响应,并将每个页面的 cookie 保存在各自的 jar 中。任何不符合我们策略的请求都会收到 403——每个组件都只获得它所需的网络访问权限,不多不少。

引擎
引擎是 Kitesurf 唯一面向公众的组件。它处理 Chrome DevTools Protocol(CDP)WebSocket 和 HTTP REST API,提供一个对内部测试有用的落地页,最重要的是,它存储每个会话的状态。所有其他组件都是无状态的。

使用 CDP 的优势在于客户端兼容性:Puppeteer、Playwright、chrome-remote-interface,以及真正的 Chrome DevTools 前端。将它们指向 Kitesurf,它们都能直接工作。Browser Run 也是这样工作的(关于为什么这很重要,稍后详述)。
与名字所暗示的相反,引擎实际上是 Kitesurf 组件中最简单的一个。有趣的部分在后面。
PageScript
PageScript 很好地展示了我们新 Workers 功能的强大之处:在这个例子中,就是 Dynamic Workers。在此之前,Kitesurf 根本不可能实现。
以下是 PageScript 内部工作原理的简化示意图。

每一个后续页面或进程外 iframe(OOPIF)都会使用 Dynamic Workers 启动一个长期存活的 PageScript isolate,用于处理该页面会话,其中包含一个干净的 globalThis 和 DOM document 对象。
随后,DOM 对象会被填入解析 HTML 文档并运行所有 JavaScript 脚本后的结果。为了解析 HTML 和 CSS,我们使用了 Blitz(一个模块化渲染引擎)和 Stylo(Firefox 的高性能 CSS 解析器)的部分组件,二者均用 Rust 编写。
对于每一个找到的 <script> 标签或 .wasm 文件,我们都会在同一个 isolate 内运行其 JavaScript 和 WebAssembly 代码。
是的,但 eval 呢
你可能会问,那 eval 呢?eval 处理起来更棘手,因为出于 安全原因,我们仍然不支持在 Workers 中原生执行 eval。我们也不能再启动另一个 isolate 来处理它们,因为那样它就无法访问 globalThis。
我们的解决方案是使用 Boa JS,一个用 Rust 编写的 ECMAScript 引擎,在 Workers 上编译并运行。我们基本上是在一个运行时之上再执行一个运行时,这看起来并不理想,事实也确实如此,但它足以应对我们在代码中偶尔遇到的 eval。将来,当 Workers 原生支持 eval 后,我们会从 Boa 迁移出去。
页面渲染器
这个组件本质上负责根据计算出的页面对象生成实际的像素。它的工作方式如下:

页面渲染器与引擎 Worker 以循环方式协同工作。每当引擎需要一帧时,页面渲染器会从 PageScript(也称为场景)获取页面对象,从 静态资源 中获取内部字体和图像,将所有内容栅格化到图像缓冲区中,然后以客户端可显示的格式(如 JPEG/PNG 或 PDF)将缓冲区返回给引擎。
这里的很大一部分魔力由另一个 Blitz 模块 blitz-paint 处理,该模块进而使用 Parley 将字符塑形为字形、选择字体并将文本断行。
Workers 内置的 RPC 系统:同一个应用,多个 isolate
Cloudflare Workers 拥有一个内置的远程过程调用(RPC)系统,允许你调用其他 Worker 上的方法、在它们之间传递对象,并调用这些对象上的方法。你无需担心 API schema、类型或身份验证,只需调用 remoteFunction(...params) 即可正常工作。你既能享受远程 Worker 的隔离性和资源,又不会失去使用 JavaScript 在本地访问其所有函数的便利性。
Kitesurf 使用了这套 RPC 系统:Engine Worker 通过 RPC 以单次调用从 PageRenderer Worker 调用 renderFrame(),并得到一张 PNG 作为结果。由于渲染器不持有任何页面状态(只有一个可丢弃的缓存),引擎可以在任何失败或卡住的 RPC 调用上安全地终止并重新启动它——从而使每个渲染请求自包含、可重试,并且其 isolate 廉价且可随时丢弃。
Kitesurf 通过 215,000+ 项 WPT 测试,且数量持续增长
Kitesurf 能用。它已经通过了约 215,000+ WPT 测试,而且我们每周都在新增数百项通过的测试。在这里你可以看到随时间推移的演进,直到我们启动该项目以来的最新版本:

值得注意的是,浏览器中对智能体重要的部分(例如 CSS、DOM、HTML、selection、SVG 和 XHR)已经有了良好的覆盖。即便是那些在智能体语境下可能并不特别重要的东西,比如 streams,现在也得到了不错的支持。

在性能方面,Kitesurf 表现相当不错。下面是五次 Browser Run quick-action 运行在 14-URL 语料库 上的中位数,对比 Chromium 与 Kitesurf。
指标 | Kitesurf | Chromium(热池) | Kitesurf,相对值 |
|---|---|---|---|
CPU:截图 | 380 ms | 1,173 ms | CPU 占用比 Chromium 少 3.1× |
CPU:HTML 提取 | 229 ms | 877 ms | 比 Chromium 少 3.8× |
内存:截图 | 57.8 MiB | 271.0 MiB | 比 Chromium 少 4.7× |
内存:HTML 提取 | 39.4 MiB | 273.7 MiB | 比 Chromium 少 7.0× |
挂钟时间:截图 | 1,148 ms | 637 ms | 比 Chromium 慢 1.8× |
挂钟时间:HTML 提取 | 820 ms | 472 ms | 比 Chromium 慢 1.7× |
Chromium 在秒表上胜出,是因为一个已经见过这个页面的 JIT 总是能击败一个冷启动的软件渲染器——而今天它确实如此,大约快了 1.7 倍。这一差距的大部分来自光栅化和 JPEG/PNG 编码,我们会继续优化这些部分。
但 Kitesurf 在内存和 CPU 上胜出,而这些才是真正决定你账单的东西——相比 Chromium 的用量,它低了 3-7 倍。更少的内存意味着我们可以运行更多会话、更好地扩展,并从根本上下调我们和你的成本。
最重要的测试:Kitesurf 能跑 Doom
我们在设计决策中强调了测试的重要性,但众所周知,无论你有多少测试,一个项目只有在能跑 Doom 之后才算真正完成。这是 Kitesurf 在运行 https://silentspacemarine.com/,来自我们几年前的一个小小的 Doom 实验。
今天就在 Browser Run 中试试
你现在就可以通过 Browser Run 试用 Kitesurf,在 beta 期间免费提供,但受每个账户的限额限制。
Browser Run CDP 端点现在支持将 Kitesurf 作为一个选项,因此你现有的客户端 Puppeteer、Playwright、chrome-remote-interface,或任何支持 MCP 和 CDP 的 AI 智能体,都已经可以正常工作。你只需在我们的端点中添加 browser=kitesurf 参数即可。
例如,要在 Opencode 中使用 Kitesurf,请参阅开发者文档中的与 MCP 客户端(CDP)配合使用,并使用以下配置:
{
"mcp": {
"kitesurf": {
"type": "local",
"command": [
"npx",
"-y",
"chrome-devtools-mcp@latest",
"--wsEndpoint=wss://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/devtools/browser?browser=kitesurf",
"--wsHeaders={\"Authorization\":\"Bearer <API_TOKEN>\"}"
],
"enabled": true
}
}
} 使用 Kitesurf 的另一种方式是通过Browser Run 的 Quick Actions。同样,只需在 quick action 端点中添加 browser=kitesurf,它就能正常工作。例如,如果你需要从 Wikipedia 快速截取一张截图,这样就可以了:
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-run/screenshot?browser=kitesurf' \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com"
}' \
--output "screenshot.png" 在 Chrome DevTools 中使用 Kitesurf Playground
开始探索 Kitesurf 的另一个选择是使用我们的公开 playground。你可以输入任意 URL,查看 Kitesurf 如何渲染页面并与之交互。
这个 playground 的一个有趣特性是,我们在 UI 中注入了 Chrome DevTools,因此你可以在 Kitesurf 渲染页面时检查展开的 DOM 元素、读取控制台消息并观察网络活动。更有意思的是,我们为 Memory 面板实现了必要的 CDP 指令,以报告每个 isolate 的 WebAssembly 占用情况,包括帧,这样你就能清楚地了解每个页面正在消耗的资源。

请查看我们的 开发者文档,了解如何将 Kitesurf 与 Browser Run 配合使用的所有细节。
Kitesurf 在什么时候更胜一筹?
截至目前,Kitesurf 能够正确渲染诸如 TodoMVC(vanilla、React、Vue、Angular、Preact)、Wikipedia、Hacker News、Cloudflare Blog 以及 Cloudflare dashboard 的大部分内容等页面。我们将持续改进 Kitesurf,提高 WPT 测试的通过率,以提升对更复杂网页的兼容性。
Kitesurf 非常适合那些需要渲染页面、但可以接受不使用功能齐全、像素级精确的 Chromium 浏览器这一取舍的 AI 智能体。它也非常适合依赖一次性 Quick Actions的自动化和应用,例如从页面提取内容,或为兼容的网站生成 PDF 或截图。
可以把 Kitesurf 看作一个临时的、完全隔离的、无状态的引擎,专为仅在一个任务持续期间存在而设计,能够很好地应对突发性的、AI 驱动的工作负载。
Kitesurf 目前还做不到的事
如果你需要播放视频、渲染 WebGL、用真实 TLS 指纹完成机器人挑战握手,或者启动一个需要持久状态的十分钟认证会话——Kitesurf 目前还不是合适的选择。直接使用 Browser Run 的默认方案即可,它由 Chromium 驱动。
判断某个特定网站是否与 Kitesurf 兼容,最好的办法就是亲自试一试。你可以通过 API 来试,或者更快捷地,在我们的公共演练场里试一试。
探索 DevTools 面板,看看幕后发生了什么,尤其要关注控制台和内存指标。
未来走向
Kitesurf 诞生已有十二周。首次提交是在五月。以下是我们正在积极推进的一些事项:
- 更完善的 CDP 覆盖。Kitesurf 实现了 CDP 协议的一个子集——足以覆盖大多数智能体和自动化工具的需求,包括稳健的 DOM 和网络检查——我们会继续扩展其能力,力求尽可能完整。
- 渲染保真度,针对截图和 PDF,因为我们知道 LLM 往往能从图像中获得比底层文本更好的效果。
- WPT 覆盖率。我们正在快速迭代,添加更多 Web API 并通过更多 WPT 测试,朝着让 Kitesurf 达到生产可用状态的目标迈进。
- 效率。我们持续运行 CPU、内存和实际耗时基准测试,并与其他开发者平台团队紧密合作,让 Kitesurf 尽可能经济高效。
最后几点
感谢你一路读到这里——我们知道这是一篇很长且技术性很强的博客文章,但希望它足够有趣。我们之所以写得如此详细,是因为我们深知构建一个新浏览器——即便是一个非常专用的浏览器——有多么重要,也有多么复杂,我们对此绝不轻慢。
Kitesurf 仍处于早期阶段,但我们希望能尽快向你开放,并从你的反馈中学习。团队将通过频繁更新积极改进它,重点聚焦于性能、效率和兼容性。
最后一件事:我们准备在就绪后开源 Kitesurf——希望很快。我们的目标是让任何客户都能在自己账户上部署自己版本的 Kitesurf,如果他们愿意的话。
所以,去playground试试吧,关注我们的changelog,并来Discord与团队交流。分享你的体验并给我们反馈;我们会认真倾听。
Introducing Kitesurf: The agent-first browser that runs in V8 isolates on Cloudflare Workers
Should we build our own browser?
This is one of those questions that has come up every few months internally at Cloudflare for years. Unsurprisingly, it’s the kind that triggers long threads with multiple reasons and persuasive arguments on why we should do it. The browser is obviously the most important software we use every day on our computers; it’s arguably the operating system of the Internet. We’re a company on a mission to help build a better Internet — who wouldn’t want to take on the challenge of building a new browser?
But we never quite found the balance between the technical difficulty of such an endeavour and the unique problems we’d be solving by doing it. And so, the idea was shelved, over and over again. Until now.
Something magical happened: we reached a tipping point where a series of powerful technical advancements in our Developer Platform became a reality, while the advent of AI agents and the demand for a new kind of browser became critical at the same time.
Running WebAssembly (Wasm) in Workers is now very mature. Primitives like dynamic workers, SQLite-based Durable Objects, Worker-to-worker RPC, service bindings, higher NodeJS compatibility and higher limits open doors to much more ambitious and complex applications that were simply not possible before.
Browser Run, our headless browser automation API product, has seen tremendous growth with the rise of AI. Agents need browsers in order to perform many tasks, and in many cases cannot succeed without them.
But there's a problem — browser engines like Chromium were built for humans, not agents, and they come with overhead that AI models simply do not need. They consume so much memory and compute that providing every agent with its own instance is prohibitively expensive, restricting large parts of the Web to only the most sophisticated and costly AI models with higher parametric knowledge, while locking out many other agentic applications.
We should be giving all agents a browser that excels at what’s important for an AI model, even if that means being light on what’s only useful for humans. For example:
- AI doesn’t care about tabs, themes, browser extensions, or synchronization across devices. It cares about token count, context windows, scalability, performance, and costs.
- Structured, machine-readable content is important, but visual perfection, smooth 60-fps scrolling is not. Agents will be just fine if the CSS parsing is slightly off or the rendering isn’t pixel perfect.
- The threat model in the context of AI using a browser is different. New problems like prompt injection and tool safety are top priorities.
Faced with these realizations, 12 weeks ago we asked the question again: Should we build our own browser? This time the answer was unanimous: Yes!
Today we are announcing Kitesurf, a new browser that runs entirely on top of Workers that we built specifically for agents, available for free while in beta in Browser Run.
Kitesurf is significantly more efficient in CPU and memory consumption than Chromium for common agentic tasks like screenshots and HTML extraction. What follows is the story of how we built it. Buckle up, it’s going to get technical — but we promise to keep it interesting.
How it started
Kitesurf started as many other great ideas have started at Cloudflare. Someone found something interesting, and the next thing you know they end up “nerd sniping” the rest of the team with a seemingly impossible but very attractive idea.
We got the initial inspiration from obscura, a headless engine written in Rust for AI automation that has “no Chrome, no Node.js, no dependencies.”

Then, with the help of an AI agent, we tried to port it to Workers. It didn't work very well at first. But once we gave the AI a solid plan and a clear definition of success — detailed enough for the agent to loop endlessly and ask questions when needed — it did work.
Blown away by this (barely) working proof of concept, we decided to let the team cook.
Design decisions
Here are some of the design decisions we made before we started.
Tests, tests, tests
We knew that moving from a prototype to a full-blown browser that could actually be useful for tasks at scale in production would take a lot of work and iteration. We won’t hide that using AI to accelerate the process was key. But how do you use AI in such a complex project, keeping the quality of both code and results under control without losing velocity? The answer is to provide as many tests as you can.
Enter the Web Platform Tests (WPT), the ideal setup: an extensive suite of success criteria that gave the AI agents clear goalposts for assessing feature conformance. We curated the selection and order of features to assign to the agents, allowing humans to focus on architectural work and reviewing the agents' approaches.
However, WPT tests only go so far: they measure conformance to W3C standards, not a browser's ability to render and interact with real-world websites. To bridge this gap, we implemented acombination of integration testing and visual regression testing — it runs multistep Puppeteer tests on real websites against both Chromium and Kitesurf not only by comparing the assertions that it makes, but also rendering outputs at every step to highlight any unwanted differences.
Use Rust when possible
Cloudflare has been working on providing great support for WebAssembly (Wasm) in Workers for quite some time. This is great because we can use high-performance C, C++, and Rust packages and compile them to Wasm. If we use Emscripten (for example) and its many layers of mocked dependencies, the compiled binary can get bulky and slow.
Instead, we opted for native Rust whenever possible and to compile directly to WebAssembly using wasm-bindgen, thus avoiding unnecessary emulation layers and running as close to the metal as possible, reliably.
Exception handling
A browser must render the whole unreliable and sometimes hostile web without ever dropping the page it's holding, so exception handling is more than just hygiene — it's how the application survives bad input without just crashing outright.
So we committed to one rule up front: any failure degrades to a blank frame or a missing element, never a dead session. Catch faults at every boundary, default to something safe and empty, and log enough to diagnose.
Isolation
Contrary to running a browser on your laptop (where you're visiting sites you trust, and it's acceptable to share some resources between them), an agent is pointed at whatever a task demands: arbitrary code from arbitrary origins.
So we built this browser on the assumption that every page load is untrusted input and every session starts fresh. Each component is isolated and has access only to the resources strictly necessary for its function.

This seems like a perfect fit for Cloudflare Workers, whose security model is built around isolation by design. But the platform only gets us the boundary between isolates. We still have to enforce the same principle at the application level, deciding what each component is allowed to touch and making sure nothing leaks across a page it shouldn't.
Stateless whenever possible
State is what makes failure expensive — if there's nothing to reconstruct, recovering from a crash is just starting a new one and replaying the request. A stateless component is disposable and parallel by nature: kill it the moment it stalls, run a thousand at once, and size them to demand instead of keeping things warm. That fits automation perfectly, where load arrives in bursts and the cheapest thing you can do is spin up work that costs only what it used and vanishes when it's done. In short, wherever a component can be stateless, it should be.
How we built it
Armed with a good plan, extensive tests, and a good tooling environment, we were ready to get started beyond the initial proof of concept. This is Kitesurf’s very high level life of a request that still holds today:

Let’s dive into the three main components that make Kitesurf work: the Engine, PageScript, and PageRenderer.
Fetching from origins
In order to render an untrusted web page, a browser has to fetch arbitrary assets — images, fonts, CSS, JavaScript, and Wasm files — off the Internet. This is one of the most dangerous operations a browser can do.
Kitesurf does it through one single component, the SandboxOutbound worker, and nothing else can touch the network directly — enforced by Dynamic Workers. The Engine uses it to bootstrap the page, fetching the main document and its scripts, and PageScript fetches everything else: stylesheets, images, fonts, and the page's own fetch() calls.
We use SandboxOutbound to enforce CORS, inject browser-shaped headers, filter responses, and keep each page's cookies in their own jar. Anything that fails our policy gets a 403 — each component gets precisely the network it needs and nothing more.

The Engine
The Engine is the only public-facing component of Kitesurf. It handles the Chrome DevTools Protocol (CDP) WebSocket and HTTP REST APIs, serves a landing page that is useful for internal testing purposes and, most importantly, stores each session state. All other components are stateless.

The advantage of using CDP is client compatibility: Puppeteer, Playwright, chrome-remote-interface, and the actual Chrome DevTools frontend. Point them at Kitesurf and they will all just work. This is also how Browser Run works (more on why this is important later).
Contrary to what the name suggests, the Engine is actually the simplest of the Kitesurf components. The fun parts come next.
PageScript
PageScript offers a good example of the power of our new Workers features: in this case, Dynamic Workers. Kitesurf simply wouldn’t have been possible before this.
Here’s a simplified diagram of how PageScript works internally.

Every next page or out-of-process iframe (OOPIF) uses Dynamic Workers to spin up a long-lived PageScript isolate that handles the page session, consisting of a clean globalThis and the DOM document object.
The DOM object is then populated with the results of parsing the HTML document and running all the JavaScript scripts. For parsing the HTML and the CSS we use parts of Blitz, a modular rendering engine, and Stylo, Firefox’s high-performance CSS parser, both written in Rust.
For each found <script> tag or .wasm file we run the JavaScript and WebAssembly code inside the same isolate.
Yes, but evals
What about evals, you ask? Evals are trickier to handle because for security reasons we still don’t support eval natively in Workers. We can’t spin another isolate to handle them either, because it wouldn’t have access to globalThis.
Our solution is to use Boa JS, an ECMAScript engine written in Rust, to compile and run on Workers. We are basically executing a runtime on top of a runtime, which doesn’t seem optimal, and it isn’t, but it works well enough to handle the occasional evals we find in the code. In the future, when native eval support lands in Workers, we will migrate away from Boa.
PageRenderer
This component is essentially responsible for generating the actual pixels from the computed page objects. Here’s how it works:

PageRenderer works in a loop with the Engine Worker. Every time the engine needs a frame, PageRenderer gets the page object from PageScript (also known as the scene), fetches the internal fonts and images from Static Assets, rasterizes everything into an image buffer, and then returns the buffer to the engine in a format that the client can display like a JPEG/PNG or PDF.
A big part of the magic here is handled by another Blitz module, blitz-paint, which in turn uses Parley for shaping the characters into glyphs, choosing fonts, and breaking text into lines.
Workers’ built-in RPC system: same application, multiple isolates
Cloudflare Workers have a built-in remote procedure call (RPC) system that allows you to call methods on other Workers, pass objects between them, and call methods on those objects. You don’t have to worry about API schemas, types, or authentication, you just call remoteFunction(...params) and it works. You benefit from the isolation and the resources of the remote Worker without losing the convenience of accessing all of their functions locally using JavaScript.
Kitesurf uses this RPC system: the Engine Worker calls renderFrame() from the PageRenderer Worker over RPC using one single call and gets a PNG as the result. Because the renderer holds no page state (only a disposable cache), the engine can safely kill and relaunch it on any failed or stuck RPC call — making each render request self-contained, retryable, and its isolate cheap and throwaway.
Kitesurf passes 215,000+ WPT tests and growing
Kitesurf works. It already passes around 215,000+ WPT tests, and we are adding hundreds of passing tests every week. Here you can see the evolution over time, up to the latest version since we started the project:

It’s worth noting that the parts of a browser that are important to agents (e.g., CSS, DOM, HTML, selection, SVG, and XHR) have good coverage already. Even things that might not be particularly important in the context of agents, like streams, are now decently supported.

Performance-wise, Kitesurf is doing pretty well. Below are the medians of five Browser Run quick-action runs across a 14-URL corpus comparing Chromium with Kitesurf.
Metric | Kitesurf | Chromium (warm pool) | Kitesurf, relative |
|---|---|---|---|
CPU: screenshot | 380 ms | 1,173 ms | 3.1× less CPU than Chromium |
CPU: HTML extraction | 229 ms | 877 ms | 3.8× less than Chromium |
Memory: screenshot | 57.8 MiB | 271.0 MiB | 4.7× less than Chromium |
Memory: HTML extraction | 39.4 MiB | 273.7 MiB | 7.0× less than Chromium |
Wall time: screenshot | 1,148 ms | 637 ms | 1.8× slower than Chromium |
Wall time: HTML extraction | 820 ms | 472 ms | 1.7× slower than Chromium |
Chromium wins the stopwatch because a JIT that has already seen this page always beats a cold software renderer — and today it does, by about 1.7x. Most of that gap comes from rasterization and JPEG/PNG encoding, which we will keep optimizing.
But Kitesurf wins on memory and CPU, the things that actually drive your bill, by 3-7x compared to what Chromium uses. Less memory means we can run more sessions, scale better, and fundamentally lower both our costs and yours.
The most important test of all: Kitesurf runs Doom
We highlighted the importance of testing in our design decisions, but we all know that no matter how many tests you have, a project isn't truly complete until Doom runs on it. Here’s Kitesurf running https://silentspacemarine.com/ from our little Doom experiment a few years ago.
Try it today in Browser Run
You can try Kitesurf with Browser Run today, available for free while in beta, behind per-account limits.
The Browser Run CDP endpoint now supports Kitesurf as an option, so your existing client Puppeteer, Playwright, chrome-remote-interface, or any AI Agent that speaks MCP and CDP, already works. All you need to do is add the browser=kitesurf parameter to our endpoints.
For example, to use Kitesurf with Opencode see Using with MCP clients (CDP) in our developer documentation and use this configuration:
{
"mcp": {
"kitesurf": {
"type": "local",
"command": [
"npx",
"-y",
"chrome-devtools-mcp@latest",
"--wsEndpoint=wss://api.cloudflare.com/client/v4/accounts/<ACCOUNT_ID>/browser-run/devtools/browser?browser=kitesurf",
"--wsHeaders={\"Authorization\":\"Bearer <API_TOKEN>\"}"
],
"enabled": true
}
}
} Another way to use Kitesurf is with Browser Run’s Quick Actions. Again, just add browser=kitesurf to the quick action endpoint and it will work. For example, if you need a quick screenshot from Wikipedia, this will work just fine:
curl -X POST 'https://api.cloudflare.com/client/v4/accounts/<accountId>/browser-run/screenshot?browser=kitesurf' \
-H 'Authorization: Bearer <apiToken>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://example.com"
}' \
--output "screenshot.png" Use the Kitesurf Playground with Chrome DevTools
Another option to start exploring Kitesurf is to use our public playground here. You can type in any URL to see how Kitesurf renders the page and interact with it.
One interesting feature of the playground is that we inject Chrome DevTools in the UI, so you can inspect expanded DOM elements, read console messages, and watch network activity while Kitesurf renders pages. More interestingly, we implemented the necessary CDP instructions for the Memory panel to report the WebAssembly footprint of each isolate, including frames, so you can gain a clear understanding of the resources each page is consuming.

Check our Developer Documentation for all the details on how to use Kitesurf with Browser Run.
When is Kitesurf better?
As of today, Kitesurf correctly renders pages like TodoMVC (vanilla, React, Vue, Angular, Preact), Wikipedia, Hacker News, the Cloudflare Blog, and much of the Cloudflare dashboard. We will keep improving Kitesurf and increasing the percentage of WPT tests that pass, to improve compatibility for more complex web pages.
Kitesurf is great for AI agents that need to render pages but can accept the trade-offs of not using a full-featured, pixel-perfect Chromium browser. It is also excellent for automations and applications that rely on one-shot Quick Actions, such as extracting content from a page or generating PDFs or screenshots, for compatible sites.
Think of Kitesurf as an ephemeral, fully-isolated, stateless engine designed to exist only for the duration of a task, that scales well for bursty, AI-driven workloads.
What Kitesurf is not yet able to do
If you need to play video, render WebGL, negotiate a bot-challenge handshake with real TLS fingerprints, or start a ten-minute authenticated session that requires persistent state — Kitesurf isn’t yet the right option. Just use Browser Run’s default, which is powered by Chromium.
The best way to know if a specific site is compatible with Kitesurf is to try it. You can do this by using the APIs or, more quickly, try it in our public playground.
Explore the DevTools panels and see what’s happening behind the scenes, with particular attention to the console and the memory metrics.
Where it goes
Kitesurf is twelve weeks old. The first commit was in May. Here are some of the things we're actively working on:
- Better CDP coverage. Kitesurf implements a subset of the CDP protocol — enough to cover the requirements of most agents and automation tools, including robust DOM and network inspection — and we continue to expand its capabilities to be as complete as possible.
- Rendering fidelity for screenshots and PDFs, because we know that LLMs can often work better from an image than from the underlying text.
- WPT coverage. We are iterating rapidly to add more web APIs and pass more WPT tests on the road to making Kitesurf production-ready.
- Efficiency. We keep CPU, memory, and wall time benchmarks running all the time and are working hand-in-hand with other Developer Platform teams to make Kitesurf as cost-effective and efficient as possible.
Final notes
Thank you for making it all the way here — we know this was a long and technical blog post, but hopefully an interesting one. We went into detail because we don't take lightly how important, but also how complex, it is to build a new browser, even a very specific one.
Kitesurf is in its early stages, but we wanted to open it up to you as soon as possible and learn from your feedback. The team will be actively improving it with frequent updates focused on performance, efficiency, and compatibility.
One last thing: we're going to open source Kitesurf once we're ready — hopefully soon. Our goal is to let any customer deploy their own version of Kitesurf on their own accounts, if they want to.
So give it a try in the playground, keep an eye on our changelog, and come chat with the team on Discord. Share your experience and send us feedback; we’ll be listening.