feat(python): headers for local_browser.connect

SY
17h ago
+168-22

Why

local_browser.connect takes only a URL. To reach a CDP endpoint behind an authenticated gateway, the credentials have to go in the query string. URLs leak easily: into exception messages, tracebacks and proxy logs. So callers end up redacting them by hand. Playwright solves this with connect_over_cdp(..., headers=...).

What

browser = await local_browser.connect(
    cdp_url="https://gateway/.../cdp",
    headers={"X-Api-Key": "..."},
)
  • headers are sent with the /json/version request and with the CDP WebSocket handshake (websockets' additional_headers).
  • They follow the advertised webSocketDebuggerUrl only when it's on the same host and port as cdp_url, so a gateway can't redirect credentials elsewhere. They're never passed to the extension.
  • Names must be RFC 9110 tokens, and values may only contain visible characters, spaces and tabs, so CR/LF injection is rejected. LocalBrowserConnectOptions sets hide_input_in_errors so a rejected credential isn't echoed in the ValidationError.
  • When headers is unset, nothing changes: CDPClient.connect isn't even passed the argument.
  • Python SDK only, with a minor changeset. Independent of #3223; the two compose.

Tests

  • New tests cover headers on /json/version, resolve passing them through, the same-host rule (6 cases), connect forwarding them only when given, and rejection of unsafe names and values without echoing them.
  • The existing test_cdp_client fakes now accept the new keyword argument.
  • ruff format --check, ruff check, ty check and generate.py --check pass.
  • pytest: 798 passed. test_official_api_routes_through_and_never_closes_a_caller_http_client fails with and without this change, because it needs the built extension in packages/extension/dist.
  • Live: against a LangSmith browser sandbox's CDP gateway, /json/version with an X-Api-Key header returned a credential-free WebSocket URL, the header-authenticated WebSocket reached Chrome (Browser.getVersion), and the same WebSocket without the header was refused.

🤖 Generated with Claude Code


Summary by cubic

Adds optional headers to local_browser.connect in the Python SDK so callers can authenticate against CDP gateways without putting credentials in the URL.

  • Headers are sent with the /json/version request and the CDP WebSocket handshake, and follow the advertised webSocketDebuggerUrl only when it's on the same host and port as cdp_url; they're never passed to another host or to the extension.
  • Header names and values are validated against RFC 9110 rules, and validation errors never echo header values since they may be credentials.
  • When headers is unset, nothing changes: no new keyword argument is passed to CDPClient.connect.