getWSEndpoint drops browserURL path prefix when resolving /json/version
Bug description
getWSEndpoint() in BrowserConnector.ts uses new URL("/json/version", browserURL) to discover the WebSocket endpoint. When browserURL includes a path prefix (e.g., behind a reverse proxy or tunnel using URLs like http://host:port/t/<token>), the absolute path /json/version replaces the entire pathname of the base URL, silently dropping the prefix.
Steps to reproduce
- Run Chrome DevTools behind a reverse proxy that adds a path prefix, e.g.
http://127.0.0.1:9222/t/<token> - Pass this as
browserURLtopuppeteer.connect():puppeteer.connect({ browserURL: "http://127.0.0.1:9222/t/abc123" }) - HTTP request goes to
http://127.0.0.1:9222/json/versioninstead ofhttp://127.0.0.1:9222/t/abc123/json/version
Expected vs Actual
| browserURL | Expected | Actual (broken) |
|---|---|---|
http://host:9222/t/token |
/t/token/json/version |
/json/version |
http://host:9222 |
/json/version |
/json/version |
Root cause
const endpointURL = new URL("/json/version", browserURL);
Per WHATWG URL Standard, an absolute path (starts with /) replaces the base URL pathname entirely.
Proposed fix
const endpointURL = new URL(browserURL);
endpointURL.pathname = endpointURL.pathname.replace(/\/?$/, "/") + "json/version";
This appends json/version to the existing path instead of replacing it.
Impact
Any scenario where Chrome DevTools is accessed through a reverse proxy, API gateway, or tunnel with path-based routing. Common in cloud IDEs, containerized dev environments, and CDP-over-HTTP tunneling services.
Also affects chrome-devtools-mcp which bundles Puppeteer and exposes --browserUrl.
Source: puppeteer/puppeteer