Cloudflare за 12 недель собрала браузерный движок на Rust и Wasm, который целиком работает в V8-изолятах Workers. Он тратит в 3–7 раз меньше CPU и памяти, чем Chromium, и проигрывает ему по времени ответа.
Агенту не нужны вкладки, темы и пиксель-перфект — ему нужны токены, масштаб и цена запроса. Если выкинуть из браузера всё человеческое, остаётся движок, который помещается в обычный Worker и стоит в разы дешевле в пересчёте на сессию.
Chromium писали для людей. Агенту не нужны вкладки, расширения и плавный скролл, но нужны структурированный текст, предсказуемая цена и масштаб. Дать каждому агенту свой Chromium слишком дорого, поэтому веб оставался доступен только самым продвинутым и дорогим моделям.
Engine держит CDP-сессию и всё состояние. PageScript поднимает изолят на каждую навигацию, парсит HTML и крутит JS. PageRenderer превращает сцену в пиксели. SandboxOutbound — единственный, кто ходит в сеть.
На корпусе из 14 URL Kitesurf тратит в 3,1–3,8 раза меньше CPU и в 4,7–7 раз меньше памяти, чем прогретый Chromium, но отвечает в 1,7–1,8 раза дольше. Пройдено 215 000+ сабтестов WPT.
Вопрос «а не написать ли свой браузер» всплывал в Cloudflare каждые несколько месяцев и каждый раз откладывался: сложность не окупалась. В этот раз совпали два события. Wasm в Workers дозрел, появились Dynamic Workers, Durable Objects на SQLite, RPC между воркерами, service bindings и поднятые лимиты. Одновременно вырос Browser Run: агентам нужен браузер, без него многие задачи просто не решаются.
Движки вроде Chromium несут накладные расходы, которые модели не нужны. Памяти и вычислений уходит столько, что отдельный инстанс на каждого агента становится непозволительным. Веб остаётся доступен только самым дорогим моделям с большим объёмом знаний, а остальные агентные сценарии отсекаются.
Браузер на ноутбуке ходит по сайтам, которым вы доверяете. Агент идёт туда, куда его послала задача: произвольный код с произвольных источников. На первый план выходят prompt injection и безопасность инструментов, а не совместимость со всеми CSS-свойствами.
Толчком стала obscura — headless-движок на Rust для AI-автоматизации с девизом «no Chrome, no Node.js, no dependencies». Порт на Workers делали с помощью AI-агента, и сперва получалось плохо. Заработало, когда агенту дали внятный план и определение успеха — достаточно подробное, чтобы он мог крутиться в цикле сам и задавать вопросы по ходу.
рис. из статьи
Команда понимала: путь от прототипа до браузера, полезного в продакшене, требует множества итераций, и без AI такой темп не удержать. Отсюда пять принципов, которые задали рамку всей работе.
Web Platform Tests дают агентам чёткие критерии соответствия стандартам W3C. Люди подбирали порядок фич и занимались архитектурой и ревью подходов, а не проверкой каждого диффа глазами.
WPT проверяют соответствие стандартам, но не умение отрисовать живой сайт. Пробел закрыли интеграционными и визуальными регрессионными тестами: многошаговые сценарии Puppeteer гоняют реальные сайты одновременно в Chromium и Kitesurf и сверяют не только ассерты, но и картинку на каждом шаге.
Wasm в Workers позволяет брать быстрые пакеты на C, C++ и Rust. Но сборка через Emscripten с его слоями замоканных зависимостей даёт тяжёлый и медленный бинарник.
Поэтому выбрали нативный Rust и компиляцию прямо в WebAssembly через wasm-bindgen — без лишних слоёв эмуляции, максимально близко к железу.
Браузер обязан отрисовать весь ненадёжный и порой враждебный веб, ни разу не уронив страницу. Обработка исключений здесь не гигиена, а способ выжить на плохом вводе.
Правило приняли сразу: любой сбой деградирует до пустого кадра или пропавшего элемента, но не до мёртвой сессии. Ловить на каждой границе, по умолчанию отдавать безопасное и пустое, логировать столько, чтобы потом разобраться.
Каждая загрузка страницы считается недоверенным вводом, каждая сессия начинается с чистого листа. Компоненты изолированы и получают доступ ровно к тем ресурсам, которые нужны для их работы.
Модель безопасности Workers построена вокруг изоляции, но платформа даёт только границу между изолятами. Дальше приложение само решает, чему что позволено трогать, и следит, чтобы ничего не протекло между страницами.
рис. из статьи
Состояние делает отказ дорогим. Если восстанавливать нечего, восстановление после падения сводится к новому запуску и повтору запроса.
Компонент без состояния одноразовый и параллельный по природе: его можно убить, как только он завис, запустить тысячу копий разом и держать ровно столько, сколько требует нагрузка. Для автоматизации это подходит идеально — трафик приходит всплесками, и дешевле всего поднять работу, которая стоит по факту потребления и исчезает по завершении.
Клиент шлёт Page.navigate по CDP-вебсокету. Engine обходит и запекает статический граф модулей, поднимает через Worker Loader свежий изолят PageScript, тот парсит HTML, строит DOM и выполняет скрипты страницы. Дальше начинается цикл кадров: renderFrame по JSRPC, PageRenderer тянет сцену через RenderPort, растеризует её и отдаёт байты, а Engine пересылает клиенту Page.screencastFrame.
рис. из статьи
| Компонент | Состояние | Доступ в сеть | Время жизни |
|---|---|---|---|
| Engine | состояние сессии | через SandboxOutbound | вся CDP-сессия |
| PageScript | нет | через SandboxOutbound | одна навигация или OOPIF |
| PageRenderer | нет, только кэш | 403 deny-all | один вызов renderFrame |
| SandboxOutbound | банка cookies на страницу | единственный выход | запрос |
Таблица собрана из описаний компонентов в статье: Engine хранит состояние сессии, все остальные компоненты в тексте названы stateless.
Чтобы отрисовать чужую страницу, браузер тянет из интернета произвольные картинки, шрифты, CSS, JavaScript и Wasm. Это одна из самых опасных операций, которые он вообще делает. В Kitesurf её выполняет ровно один компонент — SandboxOutbound, и больше никто не касается сети напрямую; права раздают Dynamic Workers.
рис. из статьи
SandboxOutbound применяет CORS, подставляет заголовки, похожие на браузерные, фильтрует ответы и держит cookies каждой страницы в отдельной банке.
Всё, что не прошло политику, получает 403. Каждому компоненту достаётся ровно та сеть, которая ему нужна, и ничего сверх того.
Engine держит вебсокет Chrome DevTools Protocol и HTTP REST API, отдаёт служебную страницу для внутреннего тестирования и хранит состояние каждой сессии. Остальные компоненты состояния не имеют. По словам авторов, из всех частей Kitesurf он самый простой, вопреки названию.
рис. из статьи
Протокол даёт совместимость с тем, что уже используют: Puppeteer, Playwright, chrome-remote-interface и сам фронтенд Chrome DevTools. Направьте их на Kitesurf — и они заработают.
На том же протоколе построен Browser Run, поэтому переключение сводится к одному параметру в адресе эндпоинта.
Состояние сессии где-то жить обязано: клиент открывает соединение и ждёт, что страница между командами никуда не денется. Cloudflare собрала его в одном компоненте, чтобы остальные остались одноразовыми.
Каждая следующая страница или внепроцессный iframe (OOPIF) получает через Dynamic Workers собственный долгоживущий изолят: чистый globalThis и объект document. Без Dynamic Workers, по прямому признанию авторов, Kitesurf не появился бы вовсе.
рис. из статьи
DOM наполняется результатами разбора HTML и выполнения всех скриптов. HTML и CSS разбирают части Blitz, модульного движка рендеринга, и Stylo, быстрый CSS-парсер Firefox. Оба написаны на Rust.
Каждый найденный тег <script> и каждый файл .wasm исполняется в том же изоляте.
Нативный eval в Workers по соображениям безопасности не поддерживается, а поднять под него отдельный изолят нельзя: у него не будет доступа к globalThis.
Решение — Boa JS, движок ECMAScript на Rust, скомпилированный под Workers. Получается рантайм поверх рантайма. Авторы прямо пишут: выглядит неоптимально, так оно и есть, но редкие eval в чужом коде это обрабатывает. Когда в Workers появится нативная поддержка, от Boa уйдут.
Компонент превращает вычисленные объекты страницы в настоящие пиксели и работает в цикле с Engine. Когда движку нужен кадр, PageRenderer забирает у PageScript объект страницы (сцену), подтягивает внутренние шрифты и картинки из Static Assets, растеризует всё в буфер и возвращает его в формате, который поймёт клиент: JPEG, PNG или PDF.
рис. из статьи
Основную работу берёт на себя ещё один модуль Blitz — blitz-paint. Он опирается на Parley: та превращает символы в глифы, подбирает шрифты и разбивает текст на строки.
У Workers есть встроенный RPC: можно вызывать методы других воркеров, передавать объекты и вызывать методы уже у них. Не нужны ни схемы API, ни типы, ни аутентификация — просто remoteFunction(...params).
Engine одним вызовом renderFrame() получает PNG. Рендерер не держит состояния страницы, только одноразовый кэш, поэтому движок спокойно убивает и перезапускает его на любом упавшем или зависшем вызове. Каждый запрос на отрисовку самодостаточен и допускает повтор.
Kitesurf проходит около 215 000 сабтестов WPT и прибавляет сотни каждую неделю. Области, которые важны агентам, — CSS, DOM, HTML, selection, SVG и XHR — покрыты уже прилично; даже streams, не самая нужная агенту вещь, поддержаны сносно.
рис. из статьи
рис. из статьи
| Область WPT | Файлы | Сабтесты | Доля |
|---|---|---|---|
| encoding/ | 35 / 51 | 11 572 / 11 629 | 99,5% |
| selection/ | 60 / 127 | 33 647 / 34 054 | 98,8% |
| dom/ | 237 / 389 | 53 422 / 55 050 | 97,0% |
| svg/ | 63 / 65 | 63 / 65 | 96,9% |
| xhr/ | 3 / 4 | 18 / 19 | 94,7% |
| html/ | 893 / 1499 | 67 461 / 71 661 | 94,1% |
| css/ | 685 / 1168 | 21 137 / 25 171 | 84,0% |
| url/ | 16 / 34 | 8 250 / 9 961 | 82,8% |
| streams/ | 20 / 70 | 1 001 / 1 311 | 76,4% |
| fetch/ | 54 / 188 | 1 485 / 2 534 | 58,6% |
| wasm/ | 35 / 99 | 727 / 1 422 | 51,1% |
| webidl/ | 6 / 44 | 196 / 451 | 43,5% |
| webmessaging/ | 21 / 138 | 46 / 177 | 26,0% |
Числа считаны с отчёта WPT на картинке выше. Полный список областей — в оригинальном скриншоте.
Медианы пяти прогонов quick-action в Browser Run. Chromium работает из прогретого пула.
| Метрика | Kitesurf | Chromium (прогретый пул) | Разница |
|---|---|---|---|
| CPU: скриншот | 380 мс | 1 173 мс | в 3,1 раза меньше CPU |
| CPU: извлечение HTML | 229 мс | 877 мс | в 3,8 раза меньше |
| Память: скриншот | 57,8 MiB | 271,0 MiB | в 4,7 раза меньше |
| Память: извлечение HTML | 39,4 MiB | 273,7 MiB | в 7 раз меньше |
| Время ответа: скриншот | 1 148 мс | 637 мс | в 1,8 раза медленнее |
| Время ответа: извлечение HTML | 820 мс | 472 мс | в 1,7 раза медленнее |
Kitesurf доступен в Browser Run бесплатно на время беты, за лимитами на аккаунт. CDP-эндпоинт Browser Run принимает его как опцию, так что ваш нынешний клиент — Puppeteer, Playwright, chrome-remote-interface или любой агент, говорящий на MCP и CDP, — уже работает. Достаточно добавить к эндпоинту параметр browser=kitesurf.
{
"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
}
}
}
Конфигурация для Opencode из раздела документации «Using with MCP clients (CDP)».
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"
Быстрый скриншот через Quick Actions: тот же параметр browser=kitesurf в адресе.
рис. из статьи
Kitesurf уже корректно рисует TodoMVC на vanilla, React, Vue, Angular и Preact, Wikipedia, Hacker News, блог Cloudflare и большую часть дашборда Cloudflare. Авторы описывают его как эфемерный, полностью изолированный движок без состояния, который существует ровно на время задачи и хорошо масштабируется под всплески агентной нагрузки.
Агентам, которым нужна отрисовка страницы и которые готовы обменять пиксель-перфект Chromium на цену и масштаб.
Автоматизациям и приложениям на одиночных Quick Actions: извлечь содержимое страницы, сделать PDF или скриншот совместимого сайта.
Если нужно проигрывать видео, рисовать WebGL, проходить бот-челлендж с настоящими отпечатками TLS или держать десятиминутную авторизованную сессию с постоянным состоянием — берите Chromium, он остаётся вариантом по умолчанию в Browser Run.
Проверить конкретный сайт быстрее всего в публичном playground: смотрите консоль и метрики памяти.
Проекту двенадцать недель, первый коммит был в мае. Четыре направления работы команда называет открыто.
Сейчас реализовано подмножество протокола — столько, чтобы закрыть требования большинства агентов и инструментов автоматизации, включая надёжную инспекцию DOM и сети. Набор продолжают расширять.
Скриншоты и PDF доводят до ума отдельно: модели часто работают с изображением лучше, чем с текстом под ним.
Добавляют веб-API и проходят новые тесты — это дорога к состоянию, пригодному для продакшена.
Бенчмарки CPU, памяти и времени ответа крутятся постоянно; над стоимостью работают вместе с другими командами Developer Platform.
Клик по разделу открывает нужное место в оригинале. Cloudflare Blog не отдаёт якоря у заголовков, поэтому ссылки построены на scroll-to-text: Chrome, Edge и Safari проскроллят к тексту и подсветят его, Firefox откроет статью сверху.
obscura, порт на Workers с помощью AI-агента и пять принципов: тесты, Rust, обработка исключений, изоляция, отказ от состояния.
рис.
рис.Жизнь запроса целиком, затем разбор четырёх компонентов: сеть, Engine, PageScript, PageRenderer.
рис.
рис.
рис.
рис.
рис.Динамика прохождения WPT, покрытие по областям и таблица бенчмарков против Chromium. Здесь же — Doom из старого эксперимента Cloudflare, запущенный в Kitesurf.
рис.
рис.Параметр browser=kitesurf, конфиг MCP, curl для Quick Actions и playground со встроенным Chrome DevTools.
рис.Совместимые сайты, честный список того, что пока не работает, и четыре направления развития.
Планы на open source, ссылки на playground, changelog и Discord.
Две группы шагов: попробовать сам Kitesurf и забрать инженерные приёмы, которые работают вне этого проекта.