让浏览器扩展安全接管 Hermes 的 browser 工具:认证控制器全指南


你在浏览器里打开了一个网页,想让 Hermes 帮你操作它——填表单、翻页、截图取证。过去的做法是让它连一个“浏览器后端”:要么云端浏览器,要么本地 CDP。可问题是,你正在用的这个浏览器标签页,和 Hermes 用的那个“浏览器后端”,根本不是同一个东西。理想的方式是:你装的浏览器扩展本身成为 Hermes 的控制通道,让 agent 直接驱动你眼前这个页面——但这就带来一个尖锐的安全问题:凭什么相信一个扩展?它拿到控制权后,能做的边界在哪里?

Hermes 的回答是一个叫 browser extension controller(浏览器扩展控制器) 的认证通道:扩展通过一次性 WebSocket ticket 注册为某个会话的 browser_* 工具的精确控制器,能力被白名单严格过滤——没有 raw CDP、没有任意脚本执行、没有 console 访问。一旦绑定,就 fail-closed:控制器失联或能力不足时,任务直接失败,绝不偷偷切到另一个浏览器后端。这个功能由 PR #91535 引入(salvage 自 #85351,保留了原作者的全部提交),目前合并在上游 main 分支,尚未进入任何 release tag。

为什么需要“控制器”这条通道

Hermes 的浏览器工具(browser_navigatebrowser_clickbrowser_snapshot 等)背后可以有多种后端:Browserbase 云浏览器、Browser Use、本地 Chromium CDP、Camofox……这些都是“Hermes 自己开一个浏览器”。但“你正在用的浏览器”是另一种形态:它不是 Hermes 启动的,而是你日常操作的那个实例。

扩展控制器解决的就是这个缺口:扩展持有你的真实浏览器标签页,Hermes 通过认证的 WebSocket 通道把 browser_* 命令发过来,扩展在真实页面里执行并回报结果。命令、能力、归属全部由服务端白名单和一次性 ticket 约束。

开启配置

功能默认关闭,需要显式打开(browser.extension_control.enabled: true),并且本地 API 路径还要求 API server bearer key:

browser:
  extension_control:
    enabled: true

控制器只能为已存在的服务端会话注册;控制器主体(principal)由服务端认证状态推导,客户端自报的 principal_id 会被忽略——身份不能由客户端说了算。

能力白名单:11 项,没有裸 CDP

通过 GET /v1/capabilities 可以发现该功能是否启用、协议版本、传输名和精确的能力白名单

controller.noop
browser_back
browser_click
browser_navigate
browser_press
browser_screenshot
browser_scroll
browser_snapshot
browser_tab_activate
browser_tabs
browser_type

请求清单之外的能力会被直接过滤掉。注意这个白名单的设计取舍:raw CDP、任意脚本执行、console 访问、上传、图片提取、vision 都不在控制器协议里——扩展能帮 agent 点、填、翻、截图,但拿不到“在页面里跑任意 JS”这种核弹级能力。这是一个刻意收窄的安全边界:够用,但不可怕。

注册与连接:一次性 ticket,30 秒有效

注册流程是三步(Local API registration):

  1. 用认证的 POST /v1/browser-control/register 提交 protocol_versionsession_idcontroller_idbrowser_profile_id 和请求的 capabilities
  2. Hermes 返回一个单次使用 ticket(30 秒 TTL)和服务端绑定后的控制器作用域;
  3. 打开 GET /v1/browser-control/ws,WebSocket 需要同时带两个 subprotocol:hermes-browser-control-v1hermes-browser-control-ticket.<ticket>

ticket 绝不接受出现在 query string 里;未知、过期、复用或畸形的 ticket 在 WebSocket 升级前就会失败。连接建立后,Hermes 发送 browser.controller.command 帧(含 command_idaction、不可变 arguments 和来源 tool_call_id),控制器回 browser.controller.result(同样的 command_id + 精确布尔 ok + resulterror)。取消和超时走 browser.controller.cancel,迟到的结果被忽略。

fail-closed:绑定后绝不静默换浏览器

这是整个设计里最值得强调的一点:

  • 请求没有绑定控制器身份、或功能关闭时 → Hermes 保持原有浏览器后端不变;
  • 一旦网关把控制器主体和传输家族绑定到请求,该扩展通道就是权威的:控制器缺失、歧义、断连或能力不足 → fail closed,而不是静默切换到别的本地/云浏览器;
  • 选中精确控制器后,它的结果或错误就是最终答案,Hermes 绝不会用另一个后端重试同一个动作

换句话说:一个“控制这个标签页”的会话,永远不会在你没注意的时候悄悄跳去云端浏览器执行。传输断了算可恢复的掉线(在途工作保留到各自 deadline),显式发 browser.controller.detach 才是硬分离(立即取消在途工作)。不同 controller id 或 browser profile 出现在同一认证会话里,是硬替换:旧的在途工作先取消,后继者才可路由。

当前状态与上手建议

  • 代码位置feat(browser): authenticated extension controller (salvage #85351),PR #91535,2026-08-21 合并到 main
  • 发布状态:尚未进入任何 release tag(v0.20.5 的 tag v2026.8.19 在其合并之前切出),目前只在 main 分支可用;等它随下一个版本发布后,hermes update 即可获得。
  • 配套流程:与它配对的浏览器扩展配对流程(loopback pairing,PR #88203)还在 open 状态——也就是说“扩展装好后一键点同意拿 token”的体验还没合并,当前需要自己走 API server 认证 + register 流程。

想先体验的话:升级到包含该 PR 的 main 构建,打开 browser.extension_control.enabled,配好 API server bearer key,然后用你熟悉的 WebSocket 客户端按上面的三步注册一个测试控制器,从 controller.noop 开始验证通道。浏览器工具的后端选择逻辑和更多模式,可以回顾我们的 Browser Use CLI 3.0 模式指南桌面浏览器读写预览

这个功能的真正价值不在“多一种连接方式”,而在于它第一次把“用户正在用的浏览器”变成了一个安全可控的 agent 执行环境:能力白名单收窄攻击面,一次性 ticket 防重放,fail-closed 杜绝后端悄悄漂移。等配对流程合并,装上扩展、点一下同意,Hermes 就能在你眼前这个页面里干活了。