安全
本桥接对你的浏览器承诺什么、每项承诺由什么把守, 以及承诺止步于何处。机制细节与每一项已接受的残余风险见信任边界台账; 每个工具能触及什么见工具风险矩阵。
一句话说清标准线
你安装的程序无法在你毫无察觉的情况下使用你的浏览器。
标准线不是什么: 它不是「任何东西都永远碰不到你的浏览器」。你安装这个桥接, 就是为了让 AI 代理 (agent) 能操作你的浏览器。标准线是本项目对自己的要求, 并非声称今天在所有地方都已达到。
有一处刻意未达标。在已批准的源 (origin) 上, 包括脱敏后的 Cookie 与存储在内的读取操作无需逐个操作确认即可运行, 而标签页标题和 URL 则完全无需批准就能读取。我们刻意止步之处列出读者最先会遇到的缺口; 台账承载每一项残余风险。
利害所在, 以及谁受信任
资产:
- 你已认证的浏览器会话: Cookie (包括 httpOnly) 与 Web 存储中的令牌。谁拿到它们, 谁就能在你已登录的站点上以你的身份行事, 绕过你已经输入过的密码和第二因素。
- 你能看到的页面内容。
- 以你的身份行事的能力: 点击、填写、导航、运行代码。
- 线路协议: 一条损坏的流可以让桥接挂起或崩溃。
| 参与者 | 是否受信任? | 说明 |
|---|---|---|
| 你 | 是 | 拥有这台机器和浏览器配置文件 |
| MCP 客户端 (Claude Code、Codex 等) | 是, 这是设计使然, 配对之后 | 由你配置; 由它驱动工具 |
| Rust 二进制 (MCP 服务器与原生消息主机) | 是 | 我们要保护的对象 |
| MV3 扩展 | 是 | 与不受信任的页面代码并肩运行 |
| 网页 | 否 | 可能被攻击者控制; 可能承载提示注入 |
| 其他本地用户与进程 | 否 | 可能尝试触达桥接套接字 |
| 网络 | 不在范围内 | 没有远程攻击面; 一切都在 localhost 或 stdio 上 |
模型所依赖的三条假设:
- 单用户机器。 没有敌意的本地用户与你共用 UID。其他用户和其他同用户二进制会被拒绝; 运行本二进制的同用户攻击者则不会 (见非目标)。
- MCP 客户端是受信任的。 你自己安装的恶意客户端已经拥有你授予它的一切。这些工具存在的目的就是被该客户端驱动。
- Chrome 的沙箱和扩展模型成立。 桥接依赖 MV3 在内容脚本与页面 JS 之间的隔离, 以及 Chrome 对主机权限的强制执行。
不要成为最廉价的那扇门
在每一种操作系统上, 直接窃取 Cookie 都已经要让攻击者付出一些代价。一个任何本地程序都能免费访问的桥接, 代价比那还低, 而攻击者总是选最廉价的那扇门。
| 平台 | 直接读取 Cookie 存储 | 低门槛的 localhost 桥接 |
|---|---|---|
| macOS | Cookie 密钥存放在登录钥匙串 (Keychain) 中; 除非用户此前已允许过该应用, 否则另一个应用读取它会触发钥匙串提示 | 静默 |
| Windows | 应用绑定加密 (app-bound encryption): 一个 SYSTEM 权限的服务只向浏览器本身释放密钥, 因此同用户进程需要 SYSTEM 权限或向浏览器注入代码 | 静默 |
| Linux | 密钥存放在会话的密钥存储 (secret store) 中, 或是一个固定的回退值; 处于已解锁会话中的同用户进程读取它不会有任何提示 | 静默 |
左列描述的是浏览器厂商公布的保护措施。这是关于其他软件的说法, 不是本项目所测试的内容。
结论: 一个静默的桥接会拉低机器原有的门槛。在门槛本已很低的地方, 比如 Linux 上, 桥接至少不能再添一扇免费的门。
谁在攻击, 各由什么应对
按本项目对现实中发生频率的判断排序, 而非测量结果: 代理正在阅读的页面排第一, 你安装的、并非已配对客户端程序 (harness) 的软件排第二, 已经以你的身份运行的持久化恶意软件排第三。第三类按设计不在范围内; 桥接只是拒绝让它的工作变得更容易。
| 威胁 | 由什么应对 | 详情 |
|---|---|---|
| 页面诱导代理在未经批准的情况下对其操作 | 页面级工具只在你批准过的源上运行; 新的源会提示你并请求主机权限; 页面无法把自己加进去 | 边界 4 |
| 提示注入: 页面内容诱骗模型发起危险的工具调用 | 对代理而言, 页面内容是数据, 不是命令; 危险操作在页面无法触及的窗口上确认, page_eval 每次调用都确认并显示完整代码 | 你要确认什么 |
| 凭据或令牌外泄 | Cookie 与存储只读、限定在白名单范围内, 并在离开扩展之前脱敏; page_text 会对密码和类似银行卡号的数字脱敏 | 工具风险矩阵 |
| 另一个本地进程劫持桥接 | 每条连接都要通过同用户检查、双向的可执行文件证明 (平台表 拥有每个操作系统测量什么这一事实), 以及一次 HMAC 质询-应答, 其每次运行的密钥从不经过线路 | 边界 2 |
| 畸形或超大消息使桥接崩溃或损坏 | 经长度检查的帧格式、让 panic 远离协议流的 panic 钩子、上报而非致命的解析错误、经模糊测试的解析器 | 边界 3 |
| 静默配对: 能写入 MCP 客户端配置的进程在无人察觉的情况下搭起整条链路 | 两个由你执行的仪式: pair 在终端键入确认之后铸造主机密钥, 浏览器登记一个 WebAuthn 凭据; 此后每一次授予都需要在场证明 | 边界 3 |
| 已吊销的客户端或主机继续行动, 因为吊销没有到达执行点 | 一份信任记录, 在每个执行点连同其纪元一起重新读取。被吊销客户端的中继被断开, 其下一个请求和重新接入被拒绝; 被吊销的主机密钥被推送给扩展, 扩展把其固定标记为已攻陷, 并关闭自己的请求门禁, 直到用户重新配对 | 边界 1 |
四跳, 以及各由什么把守
MCP client --(1)-> Rust MCP server --(2)-> native host --(3)-> extension --(4)-> web page
(trusted) (trusted) (trusted) (trusted) (UNTRUSTED)| 跳 | 谁可以跨越 | 由什么把守 |
|---|---|---|
| 1. MCP 客户端到 MCP 服务器, 经 stdio | 派生服务器的客户端程序, 配对之后 | 服务器对派生它的进程做证明, 并与已配对客户端比对; 不匹配、无法测量, 或记录不可读, 一律失败即关闭 |
| 2. MCP 服务器到原生消息主机, 经桥接套接字 | 由本用户运行、持有运行密钥 (run secret) 的本二进制 | 无监听端口; 内核对端 UID 检查; 双向可执行文件证明; HMAC 质询-应答; 声明角色的接入帧 |
| 3. 浏览器到原生消息主机, 经原生消息 | 清单只允许我们的扩展; 扩展只接受用户配对过的主机, 以其固定的密钥为准 | 清单固定扩展 id; 扩展固定主机的 P-256 密钥; 授予能力的操作需要由主机验证的在场证明: 一次 WebAuthn 断言, 或在规则不容许任何凭据作答时的确认窗口 |
| 4. 扩展到网页 | 页面请求的任何东西都不行 | 源白名单; 在页面 DOM 之外的窗口上确认; 出口脱敏; 信任状态存放在页面无法读取的存储中 |
一份记录把各跳串在一起: 运行时目录中的 trust.json 保存已配对的客户端、紧急开关 (kill switch) 闩锁, 以及各浏览器已登记的凭据, 每个执行点都以失败即关闭的方式读取它。台账中的信任记录负责它。
你要确认什么, 以及什么算在场
确认窗口。 提交与链接点击、page_press、page_select、page_eval、tab_close 和 page_upload 都在一个扩展自有、运行在自己进程中的窗口上确认, 页面无法读取、聚焦、覆盖、自动点击或自动关闭它。超时、窗口被关闭, 或提供者缺失, 都视为拒绝。
- 每一次
page_eval与page_upload调用都重新确认。 每种确认显示什么、宽限期多长, 由失败即安全的默认值负责。 - 一次已批准的点击覆盖后续重复, 以该宽限期的按标签页键为准。
page_eval永远不在其中。 - 低风险工具不经提示即运行, 只要在已批准的源上: 导航、
page_text、tab_list、脱敏后的 Cookie 与存储读取。
在场。 移除能力没有任何阻力: kill、revoke 与 uninstall 不需要证明, 因为失败即关闭就是安全状态。授予或恢复能力则需要一次「有人在场」的证明, 且恰好被一次操作消耗:
| 界面 | 证明 | 它阻止什么 |
|---|---|---|
| 扩展, 在有已登记凭据的浏览器上 | 在浏览器认证器上的一次 WebAuthn 轻触 (平台生物识别、PIN 或安全密钥), 由主机对照该浏览器下登记的凭据验证 | 任何不是由该已登记凭据签名的应答; 这一手势本身证明了什么, 是认证器的事, 见台账中的硬件残余风险 |
| 扩展, 在规则不容许任何凭据的地方: 浏览器自己的操作没有可用凭据, 登记时机器上没有任何凭据 | 在确认窗口上的一次点击, 标注为软件确认 | 静默与脚本化的操作, 而非敌意的同用户进程 |
CLI (pair、pair-client、unkill、policy set) | 在真实终端上键入的一句话; 管道化的 stdin 在任何提示之前即被拒绝 | 脚本化、管道化和意外的授予, 而非一个分配了 pty 的同用户进程 |
已登记的浏览器绝不会被降级到窗口。门禁之后的操作, 以及各由谁作答:
- 解除紧急开关: 该浏览器自己的凭据;
- 登记另一个浏览器: 任一已登记凭据;
- 策略的
presenceConfirm开启时的page_eval与page_upload: 该浏览器自己的凭据, 请求指名该操作与页面的源; - 铸造主机密钥: CLI 的终端;
- 配对客户端与放宽策略 (一次
policy set、一次放宽的回滚, 或首个基线): 从选项页出发用该浏览器自己的凭据, 在该浏览器未登记任何凭据时用窗口, 或用 CLI 的终端。policy set无论其编辑朝哪个方向都要求这一证明。收紧只有走下文的限制通道才免于证明。
两条策略通道。 授予通道 (policy set、放宽的回滚、选项页上的放宽) 在上述在场证明之后由主机密钥签名。限制通道 (policy restrict、只收紧的回滚、选项页上的收紧) 以未签名形式自由传递, 因为伪造的限制只能移除能力。命令由 CLI 页面负责; 放宽每道门禁的代价由默认值表负责。
我们刻意止步之处
下面每一个缺口都是有意接受的。它链接到的台账条目说明了什么限定了它。
- 已批准站点上不做逐次读取提示。 几乎每一步都弹提示, 会让你养成直接点掉那些守护危险操作的提示的习惯。负责方: 读取外泄。
- 已批准站点上的点击宽限期。 这是上文宽限期的代价: 无关的同源代码可以在其持续期间搭一次批准的便车。负责方: 默认值表。
- 接受同用户重新执行我们自己的二进制。 证明机制拒绝的是不同的程序, 而非由同用户攻击者启动的真正二进制。负责方: 边界 2。
- 被攻陷的已配对客户端程序保留其已准入的身份。 证明机制识别的是二进制而非意图, 因此一个变得恶意的已配对客户端在被吊销之前始终受信任。负责方: 边界 1。
- 软件下限是明确标注的, 不是隐藏的。 未登记浏览器的窗口和 CLI 的终端证明的是在受信任界面上的意图, 而非硬件, 每一次经审计的授予都记录授权它的路径,
pair的也包括在内。负责方: 边界 3。 - 从认证器上删除的凭据仍保持登记。 主机永远不会得知某个认证器或操作系统的通行密钥存储丢弃了一个凭据, 所以该浏览器会一直拒绝窗口, 并失去其以在场为门槛的操作, 直到
revoke <browser>忘记这次登记。负责方: 边界 3。
标准线今天在各系统上的落实
在每一种操作系统上, 客户端程序准入都只在配对了一个客户端之后才强制执行; 在此之前, 服务器为任何派生它的进程提供服务, 并在每次启动时以 ERROR 级别记录这种姿态。
| 操作系统 | 客户端证明 | 用户在场 | 策略授予通道 | 标准线在哪里成立 |
|---|---|---|---|---|
| macOS | 派生服务器的客户端程序的代码身份, 对照已配对客户端检查 | 已登记浏览器上的浏览器认证器; 否则为窗口; CLI 用终端 | pair 把主机密钥铸造进钥匙串, 所以来自任一界面的授予都在这里签名 | 桥接只准入由本用户运行、持有运行密钥的本二进制 |
| Linux | 派生服务器的客户端程序的镜像哈希, 对照已配对客户端检查 | 同一套阶梯 | pair 把密钥铸造进 Secret Service, 或用 --file-store 铸造进 0600 文件 | 以同样的门禁成立 |
| Windows | 派生服务器的客户端程序的镜像哈希与 Authenticode 发布者, 对照已配对客户端检查; 配对见 CLI 页面 | 同一套阶梯 | pair 把密钥铸造进凭据管理器 | 只有本用户能打开的命名管道、相互镜像证明、运行密钥 |
授予通道需要一把主机密钥, 而 pair 在每一种操作系统上都会铸造一把, 所以经签名的策略基线在三者上都可用。该行背后的 Windows 残余风险见边界 2; 安全策略中的平台表负责各机制的具体状态。
对照便利优先的设计流派
浏览器自动化的一种常见设计把便利放在首位。左列以定义该流派的几项选择来描述它; 其中浏览器如何响应是关于其他软件的说法, 该列不点名任何产品。
| 要点 | 便利优先的设计流派 | 本项目 |
|---|---|---|
| 谁能访问桥接 | 一个对任何本地进程开放的 localhost 端口 | 只有由本用户运行、持有运行密钥的本二进制 |
| 调试端口及其横幅 | 一个随时对任何本地进程开放的调试端口; 浏览器的调试横幅只在有客户端附加时显示 | 没有调试端口; 横幅只在由调试器支撑的工具或选择启用的 CDP 模式持有附加时显示 (矩阵标出了是哪些工具) |
| 默认访问 | 从第一次调用起就对所有站点拥有完整访问权 | 页面操作与读取只在你批准过的站点上运行, 标签页标题和 URL 除外; 风险最高的工具在主机持有的策略下默认关闭 |
| 逐操作确认 | 无 | 受确认门禁的操作在页面无法触及的窗口上确认; page_eval 与 page_upload 每次调用都重新确认; 放宽某道门禁是一次经签名、以在场为门槛的策略变更 |
| 本地恶意软件 | 声明不在范围内, 且不做任何处理 | 同样不在范围内, 但桥接拒绝成为最廉价的那扇门: 不同的同用户程序在套接字处即被拒绝, 而一旦配对了一个客户端, 每个客户端程序都必须匹配白名单 |
明确的非目标
- 被攻陷的操作系统账户, 或运行本二进制的同用户攻击者: 与合法主机逐字节相同, 因此哈希和代码签名都无法区分它们。
- 你自己配置的恶意 MCP 客户端。
- 多用户或共享机器隔离。
- 远程攻击者: 不存在远程攻击面。
改动, 或报告
任何移动了上述内容的变更都是安全相关变更, 要经过评审标准, 并指明它移动了本页的哪一行。疑似的边界突破走报告渠道, 绝不开公开 issue。