# 调查报告:OpenAI ChatGPT 浏览器扩展 ↔ 本地 Codex CLI 的通信机制 > 调查对象:本机(macOS)Edge Profile 1 中安装的 OpenAI **ChatGPT 官方扩展**(id `hehggadaopoacecdllhhajmbjkdcmajg`,v1.2.27236.6274,"Control your browser with ChatGPT") > 调查目标:该扩展与本地 **Codex CLI(app-server 模式)** 之间如何通信;作为 dsh-browser「扩展 ↔ 本地进程」通信的生产级参考。 > 取证时间:2026-08-14。所有路径与结论均来自本机真实文件(附录给出完整证据清单)。 ## 1. 调查概述 - **动机**:dsh-browser 正在做「浏览器扩展 ↔ 本地 harness」的桥;OpenAI 的 ChatGPT 扩展与 Codex CLI 是同一问题的生产级实现,值得逆向学习其传输选择、协议与进程编排。 - **方法**:静态取证——读取扩展 manifest/background.js/侧边栏打包产物;strings 分析扩展宿主与 app-server 两个 Mach-O 二进制;读取 `~/.codex` 下的注册表、配置、会话文件;核对本机进程与端口状态。 - **范围**:通信链路、消息协议、版本协商、反向浏览器控制、安全边界;不含 Codex 云端 API 语义。 ## 2. 结论(TL;DR) - 该扩展**内嵌了一整套 Codex 客户端**:侧边栏(`codex-sidepanel/`,约 40MB)就是 Codex 的 Web UI,`side_panel` 入口为 `codex-sidepanel/index.html`,快捷键 `Cmd+Shift+Period`(`open-codex-side-panel`)。 - 扩展**不直连 Codex 云做本地控制**,而是走一条三层本地链路:**扩展后台 ↔(原生消息桥)↔ Rust 扩展宿主进程 ↔(spawn + 127.0.0.1 代理)↔ 本地 Codex app-server**。 - 桥的注册名是 `com.openai.codexextension`(Chrome 原生消息 Native Messaging),宿主清单同时注册在 Edge 与 Chrome 的 `NativeMessagingHosts/` 目录。 - 该桥由 **ChatGPT 桌面版**(`/Applications/ChatGPT.app` v26.803.61601)通过 Codex 的插件机制装入 `~/.codex/plugins/`(本机 Codex.app 已卸载,注册表仅存旧记录)。 - 反向方向:Codex agent 通过扩展的 **`debugger`(CDP)权限**控制浏览器标签页,页面截图/DOM 快照经 `codexRuntime/tabContextAsset/*` 流式回传作为 agent 上下文。 ## 3. 组件与角色 | 组件 | 本机路径 | 角色 | |---|---|---| | ChatGPT 浏览器扩展 | `~/Library/Application Support/Microsoft Edge/Profile 1/Extensions/hehggadaopoacecdllhhajmbjkdcmajg/1.2.27236.6274_0/` | 客户端 UI + 后台(Codex 前端 + native messaging 发起方) | | 原生消息宿主清单 | `~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.openai.codexextension.json`(Edge 与 Chrome 各一份) | 把 `com.openai.codexextension` 解析到扩展宿主二进制 | | 扩展宿主二进制 | `~/.codex/plugins/cache/openai-bundled/chrome/latest/extension-host/macos/arm64/ChatGPT for Chrome`(Mach-O arm64,约 1MB,Rust) | native messaging 服务端;拉起 app-server;绑定 127.0.0.1 代理 | | 宿主注册表 | `~/.codex/chrome-native-hosts-v2.json`(schemaVersion 2) | 记录「哪个安装对应哪个扩展」+ 协议版本协商 + 各路径 | | Codex app-server | `~/.codex/plugins/.plugin-appserver/codex`(Mach-O arm64,约 218MB) | 本地大脑:线程/回合管理、执行工具(shell、浏览器、computer-use) | | 浏览器控制脚本 | `~/.codex/plugins/cache/openai-bundled/chrome/latest/scripts/browser-client.mjs` | Codex 侧 Node 浏览器客户端(被 `NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S` 哈希锁定) | 扩展 manifest 关键声明: ```json { "permissions": ["alarms", "bookmarks", "debugger", "downloads", "history", "nativeMessaging", "notifications", "scripting", "sidePanel", "storage", "tabGroups", "tabs", "topSites", "webNavigation", "contextMenus"], "host_permissions": [""], "content_security_policy": { "extension_pages": "script-src 'self'; object-src 'none'; connect-src 'self' http://127.0.0.1:* http://localhost:* ws://127.0.0.1:* ws://localhost:* https://ab.chatgpt.com https://chatgpt.com https://api.openai.com https://api.openai.org; ..." }, "side_panel": { "default_path": "codex-sidepanel/index.html" } } ``` 要点:`nativeMessaging`(本地桥)+ `debugger`(CDP 控制标签页)+ CSP 白名单**只放行 127.0.0.1 / localhost 的 HTTP 与 WebSocket**。 ## 4. 总体链路 ```text ┌─ Edge 侧边栏(codex-sidepanel/index.html = Codex UI)────────────┐ │ chrome.runtime.sendMessage({type:…, windowId, restart?}) │ │ ▼ │ │ background.js ── chrome.runtime.connectNative("com.openai.codexextension") │ ◄─ {ok:true, localAppServerUrl, runtimeConfig, …} ─────────┘ └──┼────────────────────────────────────────────────────────────────┘ │ stdio JSON-RPC 2.0(Chrome 原生消息帧:4 字节 LE 长度前缀 + UTF-8 JSON) ▼ Rust 扩展宿主 "ChatGPT for Chrome" │ ① 读 ~/.codex/chrome-native-hosts-v2.json 匹配安装(协议版本协商) │ ② spawn Codex app-server(~/.codex/plugins/.plugin-appserver/codex,注入 CODEX_* 环境变量) │ ③ 在 127.0.0.1 上绑定随机端口的 HTTP/WebSocket 反向代理(proxyPort=0) ▼ Codex app-server(axum;/healthz /turns/ /v1/agent/ /realtime /sse) ▲ │ WebSocket 直连:http(s)://127.0.0.1:?clientId=sidepanel-window- │ RPC 方法:thread/start、turn/start、turn/interrupt、command/exec、 │ process/spawn、process/kill、server/resource/read、tool_request… └─ 侧边栏 Codex 客户端(chrome-extension-codex-app-D_BsVHte.js: WebSocket(this.url)) ``` **关键设计事实**:扩展不能监听 TCP,所以所有连接都是「外拨」的——侧边栏 → background(runtime 消息)、background → 扩展宿主(stdio)、扩展宿主 → app-server(子进程 spawn + 本地代理)、侧边栏 → 代理(WebSocket)。这与 dsh-browser「扩展外拨 `ws://127.0.0.1:9122`」的思路一致,但 OpenAI 多了一层「native messaging + 扩展宿主拉起 CLI」的进程编排。 ## 5. 各层协议细节 ### 5.1 侧边栏 ↔ background(chrome.runtime 消息) 侧边栏发起「确保本地 app-server 已就绪」请求,携带 `windowId` 与可选 `restart`: ```js // chrome-extension-sidepanel-Bf7FJEU3.js const resp = await runtime.sendMessage({ type: MSG_TYPE, ...(restart ? { restart: true } : {}), windowId }) // background 回复: { ok: true, nativeHostStatus, sidePanelOpen: true, localAppServerUrl, runtimeConfig, … } ``` - `localAppServerUrl` 经 `le(url, windowId)` 变换:`new URL(url)` 后追加 `?clientId=sidepanel-window-`(每个窗口一个独立客户端标识)。 - `runtimeConfig` 经 `m()` 归一化(把扩展宿主的错误串映射成 UI 文案,见 §7)。 ### 5.2 background ↔ 扩展宿主(native messaging,stdio JSON-RPC) - 通道:`chrome.runtime.connectNative("com.openai.codexextension")`,清单 `type: "stdio"`。 - 调用面:`requestHost(method, params)` → 构造 `{ id, method, params }` 经 `port.postMessage` 发出,pending map 按 `id` 关联并带超时;断线用 `alarms`(`native-transport-reconnect`、`client-heartbeat-alarm`)自动重连。 - 扩展宿主侧应答为 JSON-RPC 2.0 风格:`{"jsonrpc":"2.0","id":…,"result":…}` / `{"jsonrpc":"2.0","id":…,"error":{code,message}}`(Rust 二进制 strings 证实)。 - 通道区分 dev / internal / production:`com.openai.codexextension.dev`、`com.openai.codexextension.internal`、`com.openai.codexextension`。 方法面(`codexRuntime/*`,background.js 中 `requestHost("codexRuntime/…")`): | 方法 | 用途 | |---|---| | `codexRuntime/hello` | 握手 / 版本协商(appServerProtocolVersion、nativeHostProtocolVersion、entryId、cliVersion…) | | `codexRuntime/ensure` | 确保 app-server 运行;带 `restart` 语义 | | `codexRuntime/restart` | 重启 app-server | | `codexRuntime/openLocalFile` | 在本地编辑器打开文件 | | `codexRuntime/tabContextAsset/create\|appendChunk\|finish\|abort\|remove` | 把标签页上下文(截图/DOM 快照)流式传给 agent | ### 5.3 扩展宿主 ↔ Codex app-server(进程编排 + 本地代理) 扩展宿主(Rust,strings 含 `src/app_server.rs`)做的事: 1. 读 `~/.codex/chrome-native-hosts-v2.json`,按 `extensionId`、协议版本、平台约束找到匹配的 Codex 安装条目(当前命中 ChatGPT.app 那条,见 §8)。 2. spawn `codexCliPath`(`~/.codex/plugins/.plugin-appserver/codex`),注入环境变量:`CODEX_CLI_PATH`、`CODEX_HOME`、`CODEX_EXTENSION_ID`、`CODEX_APP_SERVER_PROXY_HOST`、`CODEX_APP_SERVER_PROXY_PORT`、`CODEX_BROWSER_CLIENT_PATH`、`CODEX_BROWSER_USE_NODE_PATH`、`CODEX_NODE_REPL_PATH` 等。 3. 在 `127.0.0.1` 上 bind 一个**随机端口**的 HTTP/WebSocket 反向代理(清单里 `proxyPort: 0` 表示由运行时自选),把 sidepanel 的连接转发给 app-server;该代理同时处理 WebSocket 升级(101 Switching Protocols / sec-websocket-key,strings 证实)。 4. app-server 本机还可能暴露 unix 控制套接字(`~/.codex/ipc/ipc.sock`,strings:`/app-server control socket is already in use at`)。 ### 5.4 侧边栏 Codex 客户端 ↔ app-server(WebSocket RPC) - 客户端类:`setLocalAppServer(e){ this.url = e.localAppServerUrl; … }`,`connect(){ const n = new WebSocket(this.url); … }`(chrome-extension-codex-app-D_BsVHte.js)。 - app-server 端点(app-server 二进制 strings):`/healthz`、`/turns/`、`/v1/agent/`、`/v1/models`、`/realtime`、`/sse`、`/codex/analytics-events/events`、`/api/codex/*`。 - RPC 方法(app-server-manager-signals chunk 中可观测到的埋点名):`thread/start`、`turn/start`、`turn/interrupt`、`command/exec`、`process/spawn`、`process/kill`、`server/resource/read`、`tool.request`、`approval.response`、`remote_control`、`desktop.turn_submit` 等——即 Codex「线程/回合/工具审批」的完整生命周期。 ## 6. 反向方向:Codex agent → 浏览器(控制链,命令级) Codex 要让 agent 操作已打开的标签页,走的是「扩展当 CDP 网关」:扩展把 Chrome DevTools Protocol(CDP 1.3)能力**原样透传**给宿主侧。background.js 的 `un(e)` 是唯一路由: `Target.getTargets` 特判为 `chrome.debugger.getTargets()`,其余一律 `chrome.debugger.sendCommand(target, e.method, e.commandParams)`——`target` 可以是 `{tabId}`(整页)或 `{targetId}`(frame/OOPIF 子 target,`debugger.attach({targetId}, "1.3")`)。 CDP 事件(`Page.screencastFrame` 等)经 `debugger.onEvent → sendCdpEvent` 回传宿主; `debugger.onDetach → sendCdpDetach`。**真正的操作逻辑不在扩展里,而在 `browser-client.mjs`**(Codex 的打包浏览器驱动,约 1.15MB,Playwright 内核 + 自定义 CDP 客户端);扩展只是「CDP 执行器」,方法名与参数由宿主侧动态下发(`e.method / e.commandParams`)。 ### 6.1 为什么不需要激活标签页 - `chrome.debugger.attach({tabId: N}, "1.3")` 可附加到**任意后台标签页**(不要求激活、 不要求可见、不要求在前台窗口),附加后即获得该 tab 的 CDP 会话并下发命令。 - 后台截图走 **`Page.startScreencast`**(`{everyNthFrame: 1, format, quality: 80}`): 渲染进程为后台标签页做**离屏渲染**并持续推送 JPEG 帧(`Page.screencastFrame` 事件), 所以「看不见的标签」也能出图;`rawScreencastTabIds` 集合管理正在 screencast 的标签页。 - 视口用 **`Emulation.setDeviceMetricsOverride`**(`WxH` 格式解析, `deviceScaleFactor: 1, mobile: false`)固定后台渲染尺寸;另有 `Page.captureScreenshot` 作整页/设备截图。 ### 6.2 为什么不影响前台鼠标 点击/输入/求值全部用 CDP **合成事件**,直接注入目标渲染进程的输入管线,**不经过系统 光标、不抢占焦点、不打断前台操作**: - **点击**:`DOM.scrollIntoViewIfNeeded`(页面内滚动,不动系统鼠标)→ `getBackendNodeViewportPoint`(把 DOM 节点换算成视口坐标)→ `Input.dispatchMouseEvent`(`mouseMoved` → `mousePressed` → `mouseReleased`, 带 `x/y/button/buttons/clickCount/modifiers`)。 - **键盘**:`Input.dispatchKeyEvent`(keyDown/keyUp,含 `Shift`/`Meta`/`ControlOrMeta` 组合键逻辑)。 - **求值/状态读取**:`Runtime.evaluate`(可带 `executionContextId` 定位指定 frame; `returnByValue: false` 拿对象引用做检查)。 - 目标定位是 **CSS 选择器 + DOM 查询**(Playwright 内核的 locator,`PlaywrightLocator`), 元素查找失败走「focused frame cycle」检测逐层下钻 iframe。 > 例外说明:`dispatchMouseMove` 里同时存在 `ui.moveMouse(x, y)` —— 那是 **Computer Use > 模式**的 OS 级真实鼠标移动(macOS 层),与纯浏览器操作(CDP 合成事件)是两条独立路径; > 本机配置 `BROWSER_USE_AVAILABLE_BACKENDS = "chrome,iab"` 优先走 Chrome 插件路径,即合成事件。 ### 6.3 一条「点击」的完整命令链 ```text browser-client.mjs(逻辑层) locator(selector) → DOM.scrollIntoViewIfNeeded → getBackendNodeViewportPoint → Input.dispatchMouseEvent{mouseMoved} → {mousePressed} → {mouseReleased} │ 每个 CDP 请求封装为 {method, commandParams, target:{tabId|targetId}} ▼(native 桥 codexRuntime/* 请求) 扩展宿主 → 扩展 background.js un() → chrome.debugger.sendCommand(target, method, params) ▼ Chrome DevTools Protocol → 目标标签页渲染进程(合成事件注入) ``` ### 6.4 页面上下文回传(tabContextAsset 管线) 扩展侧经 `requestHost("codexRuntime/tabContextAsset/…")` 与宿主协作: `create({fileName})` → `appendChunk({assetId, dataBase64})` ×N → `finish({assetId})` (中断时 `abort`,清理时 `remove`)。截图/数据以 base64 分块上传,宿主按 `tab-context.txt` / `assetId` 组织成 agent 可读的「Chrome tab context asset」。 ### 6.5 端到端路径 agent 工具调用 → app-server → node_repl MCP(`/Applications/ChatGPT.app/.../node_repl`, browser-client.mjs 由 `NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S` 哈希锁定)→ browser-client.mjs 逻辑 →(native 桥)→ 扩展 debugger/CDP → 真实标签页; 结果与 `Page.screencastFrame` 帧沿原路返回,页面上下文走 tabContextAsset 流。 > 旁证:Edge 数据目录里有 `Local State.codex-bak-allow-js-apple-events` 备份——说明本机曾手工切换 Chrome/Edge 的 `AllowJavaScriptAppleEvents` 策略(AppleScript JS 自动化),是除 CDP 之外的又一条 macOS 浏览器控制通道(与 Codex Computer Use 相关),与 native 桥相互独立。 ## 7. 版本协商与错误码 扩展宿主按协议版本匹配安装,失败时返回如下文案(= UI 文案映射表 + 二进制 strings): - `Codex Chrome native host v2 manifest is missing`(`~/.codex/chrome-native-hosts-v2.json` 缺失) - `No compatible Codex app-server entry was found` - `No Codex app-server entry matches the required protocol version` - `Codex Chrome native host is incompatible. Update both the Codex app and the Chrome extension.` - `Native transport is disconnected; reconnect is pending` / `Native transport disconnected` - 结构错误:`app_server_runtime_error`、`chrome_extension_update_required`、`codex_app_update_required`、`no_matching_codex_install`、`required_path_missing` 注册表(`chrome-native-hosts-v2.json`)关键字段:`entryId`、`installId`、`appServerProtocolVersion`(当前 2)、`nativeHostProtocolVersion`(当前 2)、`cliVersion`、`extensionIds`、`nativeHostNames`、`paths`(browserClientPath / codexCliPath / codexHome / extensionHostPath / nodePath / nodeReplPath / resourcesPath)、`proxyHost` / `proxyPort`、`presence`(pid / startedAt / lastSeenAt)。 ## 8. 本机现状(2026-08-14 取证) - 生效条目:`codex-runtime-59a87093db1104c12c824e6ceca49a1c`,appVersion `26.803.61601`,nodePath 指向 **ChatGPT.app**(`/Applications/ChatGPT.app` 存在,v26.803.61601;Codex.app 已卸载)。 - `com.openai.codexextension.json` 的 `allowed_origins` 放行两个扩展:`hehggadaopoacecdllhhajmbjkdcmajg`(ChatGPT 扩展,已装)与 `odlomjlbamekndcpllcnffbgeohgkmjh`(独立 Codex 扩展,本机未装)。 - 时间线:宿主清单 08-08 00:54 → 扩展宿主二进制 08-12 00:56 → 注册表更新 08-12 08:38 → `~/.codex/ipc/ipc.sock` 08-14 09:38(当天有 app-server 实例运行,即最近用过侧边栏)。 - 浏览器会话记录:`~/.codex/browser/sessions/*.toml`(每文件是 Codex 被授权控制的 origin 列表,如 `https://github.com`、`http://localhost:8888` 等)——Codex 只能操控已授权 origin 的标签页。 ## 9. 可复用的安全设计 - CSP 把 connect-src 锁死在 `127.0.0.1` / `localhost` 的 HTTP/WS,扩展无法外联任意服务器。 - native 宿主清单用 `allowed_origins` 锁定扩展 ID,且同时注册在 Edge/Chrome 两个浏览器。 - 版本协商 + 协议版本字段(appServer/nativeHostProtocolVersion)防止新旧不匹配。 - browser-client.mjs 用 SHA-256 白名单锁定(`NODE_REPL_TRUSTED_BROWSER_CLIENT_SHA256S`)。 - 授权模型:Codex 只能控制 `browser/sessions/*.toml` 里登记了 origin 的标签页。 ## 10. dsh-browser 采用的设计 | 维度 | dsh-browser v0.2 | OpenAI ChatGPT↔Codex(参考) | 取舍 | |---|---|---|---| | 生产传输 | Native Messaging + 经过认证的回环 WebSocket | Native Messaging + 127.0.0.1 HTTP/WS 代理 | 保留 Native Messaging 的扩展身份检查,同时让 Harness 内嵌 Provider 拥有统一协议。 | | 开发传输 | 扩展直接外拨回环 WebSocket | 开发/internal/production 原生宿主名 | Direct 模式减少本地调试步骤,但仍要求 token、扩展 Origin 和完整握手。 | | 服务端 | Harness 进程内 `BrowserBridge` | 扩展宿主拉起独立 app-server | 浏览器能力随 Harness 插件生命周期启动和静止卸载,不增加第二个 agent 服务端。 | | 页面控制 | 快照与 DOM 动作用 `chrome.scripting` 注入隔离世界;表达式用临时 `debugger` 会话调用 `Runtime.evaluate` | `debugger` 透传通用 CDP,逻辑位于 `browser-client.mjs` | v0.2 只为表达式使用 CDP,不提供通用 CDP、后台截图、跨进程 frame 或完整可访问性树。 | | 协议 | `protocolVersion`、能力列表、id、取消、结构校验和稳定错误码 | Native host/app-server 双版本与 JSON-RPC | 版本不匹配在命令前失败;Direct 与 Native 共享命令语义。 | | 恢复 | WebSocket ping/pong、Chrome alarms、Native host 有界重连 | alarms、heartbeat、宿主 presence | 重连重新握手,修改操作不跨连接重放。 | | 授权 | 安装级 HTTP/HTTPS 注入权限 + Harness 审批 | ``/`debugger` + origin 会话授权 | 扩展默认访问全部网页标签页,浏览器内部页仍拒绝;修改操作继续经过 Harness 审批。 | ## 附录:核心证据文件清单 ```text ~/Library/Application Support/Microsoft Edge/Profile 1/Extensions/hehggadaopoacecdllhhajmbjkdcmajg/1.2.27236.6274_0/ manifest.json # nativeMessaging + debugger + side_panel + CSP background.js # connectNative + requestHost + codexRuntime/* + debugger.* codex-sidepanel/index.html # side_panel 入口(Codex UI) codex-sidepanel/assets/chrome-extension-sidepanel-Bf7FJEU3.js # localAppServerUrl 交接 codex-sidepanel/assets/chrome-extension-codex-app-D_BsVHte.js # WebSocket(this.url) 客户端 codex-sidepanel/assets/app-server-manager-signals-DL-4UJGg.js # app-server RPC 埋点名 codex/build-info.json # 构建信息(sha ad34341…) ~/Library/Application Support/Microsoft Edge/NativeMessagingHosts/com.openai.codexextension.json ~/Library/Application Support/Google/Chrome/NativeMessagingHosts/com.openai.codexextension.json ~/.codex/chrome-native-hosts-v2.json # 宿主注册表(schemaVersion 2) ~/.codex/chrome-native-hosts.json # 旧版注册表(schemaVersion 1) ~/.codex/plugins/.plugin-appserver/codex # app-server 二进制(218MB, arm64) ~/.codex/plugins/cache/openai-bundled/chrome/latest/ extension-host/macos/arm64/ChatGPT for Chrome # 扩展宿主(Rust, arm64) scripts/browser-client.mjs # Codex 侧浏览器客户端 ~/.codex/ipc/ipc.sock # app-server unix 控制套接字 ~/.codex/browser/sessions/*.toml # 已授权 origin 列表 ~/.codex/config.toml # 插件/chrome/browser/node_repl 配置 ```