Skip to content

fix(browser): CDP addresses through a TLS-ending tunnel are https and wss (FD-5470) - #608

Merged
earakely-scale merged 2 commits into
mainfrom
edgararakelyan/fd-5470-cdp-origin
Nov 11, 2026
Merged

earakely-scale merged 2 commits into
mainfrom
edgararakelyan/fd-5470-cdp-origin

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Part of M8.58 (FD-5470). On a remote provider whose tunnel ends TLS, browser_connect_cdp handed agents http:// and ws:// CDP addresses for a host that only answers https and wss on 443, so their connections were refused. Now the step names where the agent reaches the env, and the browser builds the address under that origin, the way a deploy already builds an env's MCP and site URLs outside the VM.

Why it happened: Modal's tunnel ends TLS and opens a plain HTTP connection to the VM, adding no X-Forwarded-Proto (probed on a modal_vm tunnel). The driver tells the browser the scheme of the connection it accepted, which is http, so the browser minted http/ws addresses, and discovery (json/version) answered ws. browser_connect_cdp rebases only an address that starts with the env's URL, scheme included, so it couldn't repair it. Local Docker never shows it: the caller and the driver both see http.

What changes

browser/v1 cdp (agentenv_protocol.website_browser)

  • Takes an optional origin: an http or https URL, a path allowed, with no query, fragment or credentials; anything else is refused as an invalid argument before anything is minted.
  • With it, the address is the origin, the path prefix the driver forwarded, then /cdp/<token>/. The token store keeps that it was named so, by hash as before.
  • Discovery under such a token answers the WebSocket it minted. Without origin, both answer at the address the request reached, as before.

browser_connect_cdp

  • Names the env's URL as the agent's sandbox reaches it as origin, when the browser's card lists origin on cdp. A browser composed before this refuses a parameter it doesn't know, so it's asked as before and its answer rebased as before.
  • _rebased stays: a gateway's CDP answers, which it rewrites to the address the step reached it at, still need it.

The token file gains named, read as required: no caller produces a file without it. It's written and read only by the browser's own app server, beside its socket, no snapshot carries it, a computer never changes image while it runs, and a data reset revokes every token.

Not in this PR: the gateway's CDP route (browser_proxy.public_base) and its site routes' X-Forwarded-Proto (rest_proxy.forwarded_address) fall back to the connection's scheme the same way, so behind such a tunnel they'd say http too. A follow-up.

Decisions (in chat with Edgar)

  1. Build the address outside the VM, as a deploy builds the env's other URLs, rather than have the deploy tell the driver its public URL after boot, or have each sandbox provider declare a public scheme.
  2. Fix discovery in the same change: a CDP client given http_url connects to whatever json/version names, so the browser keeps the named address for it.

Test plan

  • Unit: test_browser_cdp.py mints under a named origin over a request that arrived over http and checks discovery answers wss; refuses ten bad origins without minting; keeps the named flag through a restart; the card lists origin. test_browser_connect_cdp.py sends the agent's origin for a browser on modal_vm and on local Docker, and none to a browser whose card doesn't take one. The protocol package, tst/unit/task_step and tst/unit/env/gateway: 4,802 passed, 1 skipped.
  • Integration, local Docker (Mac, arm64): new test_behind_a_front_that_ends_tls_..., with a front ending TLS in front of the browser computer (tst/util/tls_front.py, the local CA's certificate) as a remote provider's tunnel does: the address named under the front's origin is https/wss, discovery answers wss, and Playwright by http_url, Puppeteer by ws_url and Playwright by ws_url open the site through it; asked without an origin, it's ws there. The CDP clients and browser_connect_cdp tests beside it, and browser_gateway_test.py, pass.
  • Remote, modal_vm: the browser computer composed on a Modal builder from this branch and probed there (gate 3 ready). Its addresses through Modal's tunnel: named, https/wss, with json/version answering wss, and all three clients connect; unnamed, http/ws. Browser Use (Claude Sonnet 5.5) on two Online-Mind2Web tasks, each recorded: on Apple it added the cheapest blue 256GB certified refurbished iPad Air to the bag ($669, the bag page in the recording's last frames); CarMax answered every page load from Modal's network with ERR_HTTP2_PROTOCOL_ERROR, as the recording shows, though the CDP connection worked.

Docs

The user guide's browser page should say that browser/v1 cdp takes an optional origin, where the caller's agent reaches the env, which browser_connect_cdp passes, and that addresses and discovery are then https and wss through a tunnel that ends TLS.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no blocking issue remains.

Summary

The PR lets callers name the origin used for CDP addresses and discovery, so clients get https and wss through tunnels that end TLS.

  • Browser CDP addresses can use the origin agents actually reach.
  • The CDP task step sends the browser the agent's reachable origin.

Diagram

sequenceDiagram
  participant Step as browser_connect_cdp
  participant Browser as Browser
  participant Agent as Agent
  Step->>Browser: Request CDP address with agent-reachable origin
  Browser-->>Step: https and wss addresses
  Step->>Agent: Add CDP addresses
  Agent->>Browser: Read json/version through TLS tunnel
  Browser-->>Agent: Named wss address
Loading

Reviews (6) · Last reviewed commit: "fix(browser): an origin's scheme is read..." · Reviewed by Greptile

… wss (FD-5470)

browser/v1 cdp takes an optional origin, where the caller's agent reaches the env, and mints the address under it;
discovery under such a token answers the WebSocket it minted. browser_connect_cdp names the env's URL as the agent's
sandbox reaches it, when the browser's card takes an origin. Behind Modal's tunnel, which ends TLS and adds no
X-Forwarded-Proto, the browser could only see http, so agents got http and ws addresses that were refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 11, 2026 00:30
An origin named HTTPS://... passed the check but its WebSocket form came out as ws://HTTPS://...; the origin is now
rebuilt from its parsed parts, scheme in lower case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit fa61c9d into main Nov 11, 2026
63 of 65 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/fd-5470-cdp-origin branch November 11, 2026 01:45
earakely-scale added a commit that referenced this pull request Nov 11, 2026
…gin the agent's named, on containers too

browser_connect_cdp names the agent's origin on the cdp call it asks through
the multi env's gateway too, as #608 has it name it of a browser asked
directly, and the gateway keeps the https or wss of an address the browser
minted under it. So behind a tunnel that ends TLS, as Modal's, the address
and its discovery are https and wss on a VM and on Modal containers alike,
where a container gateway's tunnel URL isn't known until it runs.

The deploy no longer tells the gateway its gateway_url
(AGENTENV_GATEWAY_URL), which only a VM deploy could.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant