feat(python): send HTTP headers when connecting to a CDP endpoint
Merged
SE
13h ago+677+677+012345678901234567890123456789-25-25-01234567890123456789
changes pulled in from #3224 , thanks @sydney-runkle for the contrib!
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": "..."},
)
headersare sent with the/json/versionrequest and with the CDP WebSocket handshake (websockets'additional_headers).- They follow the advertised
webSocketDebuggerUrlonly when it's on the same host and port ascdp_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.
LocalBrowserConnectOptionssetshide_input_in_errorsso a rejected credential isn't echoed in theValidationError. - When
headersis unset, nothing changes:CDPClient.connectisn'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_clientfakes now accept the new keyword argument. ruff format --check,ruff check,ty checkandgenerate.py --checkpass.pytest: 798 passed.test_official_api_routes_through_and_never_closes_a_caller_http_clientfails with and without this change, because it needs the built extension inpackages/extension/dist.- Live: against a LangSmith browser sandbox's CDP gateway,
/json/versionwith anX-Api-Keyheader 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 go to the
/json/versionrequest and the CDP WebSocket handshake, and only to endpoints on the same host and port ascdp_url; redirects off-host or to insecure endpoints are refused rather than followed. Headers are never passed 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.
- Discovery that returns 401 or 403 now fails immediately with a message pointing at gateway credentials instead of retrying.
- When
headersis unset, nothing changes: the keyword argument isn't passed toCDPClient.connect, and redirect behavior is unchanged.