feat: add ChromeDriver proxy for WebDriver and BiDi protocol support#164
feat: add ChromeDriver proxy for WebDriver and BiDi protocol support#164tnsardesai wants to merge 2 commits intomainfrom
Conversation
Install ChromeDriver matching the Chromium version in both headful and headless images, managed via supervisord on internal port 9225. A new ChromeDriver proxy (exposed on port 9224) intercepts session creation to inject goog:chromeOptions.debuggerAddress so ChromeDriver attaches to the already-running browser, rewrites webSocketUrl in responses to route BiDi traffic back through the proxy, and transparently proxies all other HTTP and WebSocket traffic. Extracts the shared WebSocket bidirectional pump logic from devtoolsproxy into a reusable wsproxy package with a MessageTransform hook, used by both the existing DevTools proxy and the new ChromeDriver proxy. Expands CDP discovery endpoint proxying to include /json/list and makes proxy ports configurable via environment variables. Includes comprehensive unit tests for the ChromeDriver proxy and manual BiDi validation scripts (raw WebSocket, Puppeteer, Selenium). Co-authored-by: Cursor <cursoragent@cursor.com>
7941a36 to
433f3d1
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 3 potential issues.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| var ( | ||
| chromeDriverAddr = "127.0.0.1:9225" | ||
| debuggerAddr = "127.0.0.1:9222" | ||
| ) |
There was a problem hiding this comment.
Hardcoded debuggerAddr ignores configurable DevTools proxy port
Medium Severity
The debuggerAddr variable is hardcoded to "127.0.0.1:9222", but the DevTools proxy port is configurable via the DEVTOOLS_PROXY_PORT environment variable (in config.go). If DEVTOOLS_PROXY_PORT is set to a non-default value, the DevTools proxy listens on the new port, but the ChromeDriver proxy still injects debuggerAddress: 127.0.0.1:9222, causing ChromeDriver to connect to a port where nothing is listening. The Handler function doesn't accept the DevTools proxy port as a parameter.
Additional Locations (1)
| return msg | ||
| } | ||
| return maybeInjectBidiSession(msg, logger) | ||
| } |
There was a problem hiding this comment.
BiDi WebSocket session response webSocketUrl not rewritten
Medium Severity
The WebSocket message transform only processes the -> (client-to-upstream) direction for injecting debuggerAddress into session.new. It does not intercept <- (upstream-to-client) messages, so the webSocketUrl in the BiDi session.new response is not rewritten. This URL continues to point to the internal ChromeDriver address (127.0.0.1:9225) instead of the proxy, which is inconsistent with the HTTP POST /session flow where rewriteWebSocketURL correctly rewrites it.
| func isWebSocketUpgrade(r *http.Request) bool { | ||
| return strings.EqualFold(r.Header.Get("Connection"), "upgrade") && | ||
| strings.EqualFold(r.Header.Get("Upgrade"), "websocket") | ||
| } |
There was a problem hiding this comment.
isWebSocketUpgrade fails on multi-value Connection headers
Low Severity
The isWebSocketUpgrade function uses strings.EqualFold for exact comparison on the Connection header. Per HTTP/1.1, the Connection header can contain comma-separated tokens (e.g., Connection: keep-alive, Upgrade). This exact match would fail for multi-valued headers, causing valid WebSocket upgrades to fall through to the httputil.ReverseProxy instead of the BiDi-aware proxyWebSocket handler, thereby skipping the debuggerAddress injection for session.new commands.


Install ChromeDriver matching the Chromium version in both headful and headless images, managed via supervisord on internal port 9225. A new ChromeDriver proxy (exposed on port 9224) intercepts session creation to inject goog:chromeOptions.debuggerAddress so ChromeDriver attaches to the already-running browser, rewrites webSocketUrl in responses to route BiDi traffic back through the proxy, and transparently proxies all other HTTP and WebSocket traffic.
Extracts the shared WebSocket bidirectional pump logic from devtoolsproxy into a reusable wsproxy package with a MessageTransform hook, used by both the existing DevTools proxy and the new ChromeDriver proxy. Expands CDP discovery endpoint proxying to include /json/list and makes proxy ports configurable via environment variables.
Includes comprehensive unit tests for the ChromeDriver proxy and manual BiDi validation scripts (raw WebSocket, Puppeteer, Selenium).
Checklist
Note
Medium Risk
Introduces a new externally exposed proxy and changes WebSocket proxy plumbing plus runtime container networking; failures could break remote automation/CDP connectivity but the change is well-covered by unit/e2e tests.
Overview
Adds first-class ChromeDriver/WebDriver + BiDi support to the Chromium images by installing a version-matched
chromedriver, running it undersupervisordon internal port9225, and exposing a new external proxy on9224(Docker + unikernel scripts updated to publish the port).The server now starts a dedicated ChromeDriver proxy (
chromedriverproxy) that injectsgoog:chromeOptions.debuggerAddresson session creation so ChromeDriver attaches to the existing Chromium instance, rewrites returnedwebSocketUrlto route BiDi traffic back through the proxy, and forwards all other HTTP/WebSocket traffic transparently.Refactors shared WebSocket pumping into a reusable
wsproxypackage (used by both DevTools and ChromeDriver proxies), expands CDP JSON discovery proxying to include/json/list, and makes proxy ports configurable viaDEVTOOLS_PROXY_PORT/CHROMEDRIVER_PROXY_PORT. Adds unit tests for the proxy plus new BiDi e2e coverage and JS validation scripts (Puppeteer + Selenium).Written by Cursor Bugbot for commit 433f3d1. This will update automatically on new commits. Configure here.