Skip to content

feat(browser): attach passive tools to browsers without HTTP discovery - #2346

Merged
morluto merged 3 commits into
morluto:mainfrom
all-black-493:feat/browser-websocket-endpoint
Oct 13, 2026
Merged

morluto merged 3 commits into
morluto:mainfrom
all-black-493:feat/browser-websocket-endpoint

Conversation

@all-black-493

@all-black-493 all-black-493 commented Oct 12, 2026 •

Copy link
Copy Markdown

Summary

Implements option 2 from #2288. The passive browser tools can now attach to a browser that serves a loopback browser WebSocket but no HTTP discovery. Before this change, every passive call read /json/version and /json/list first, so such a browser failed with endpoint_unreachable.

The maintainer scope decision for #2288 is still pending. This PR is open for review, but per CONTRIBUTING it should not be merged until that decision covers it. The proposal and evidence are in this comment.

Problem and expected behavior

Since Chrome 136, --remote-debugging-port is ignored for the default profile (Chrome blog). Chrome 144+ can instead enable remote debugging on a running browser from chrome://inspect/#remote-debugging (Chrome blog). A third-party report (trycua/cua#2916, Chrome 151) says this mode writes the port and a /devtools/browser/<id> path to DevToolsActivePort and returns 404 for /json/version.

Expected: a caller can pass ws://127.0.0.1:PORT/devtools/browser/<id> and get the same targets and inspection results as HTTP discovery.

Change and scope

  • Endpoint contract. cdp_endpoint on the six passive tools also accepts ws://127.0.0.1:PORT/devtools/browser/<id> or ws://[::1]:PORT/.... The port must be explicit, the id one path segment, and credentials, query and fragment are rejected. The HTTP form is unchanged. localhost, LAN, wss: and /devtools/page/ sockets stay invalid. The domain parses the endpoint once into { transport, url, recorded }.
  • Tools. list_browser_targets, inspect_web_page, analyze_web_bundle, observe_web_session, discover_webmcp_tools, capture_web_screenshot. Electron, web runtime, scenario attach and V8 Inspector endpoints keep browserEndpointSchema and stay HTTP-only.
  • One socket per call. Socket discovery sends Browser.getVersion and Target.getTargets, then the target session adopts the same socket for Target.attachToTarget (flat). Consent is reportedly requested per WebSocket upgrade, so a second socket would prompt twice. Unadopted sockets are closed on failure and on cancellation.
  • Evidence redaction. Chrome refuses a browser-socket upgrade with the wrong id (HTTP 404, checked on Chrome 138), so the id gates access. Evidence records ws://127.0.0.1:PORT/devtools/browser/[REDACTED]. JavaScript runtime reconciliation accepts that recorded form for browser captures. HTTP-endpoint Evidence is unchanged.
  • Refactor. CdpBrowserProvider repeated the same discover → authorize → session → cleanup block in five operations. All five needed the new transport, so they now share one withTargetSession helper.

Not included: reading DevToolsActivePort for the caller, a pipe transport (option 3), or anything that launches a browser. #2287 separately documents option 1.

Follow the contribution scope. The scope decision is pending in #2288.

Contract and boundary impact

  • Semantic owner and earliest changed stage: browserObservationEndpointSchema in src/domain/browserObservation.ts; transport selection in src/browser/CdpTargetSession.ts.
  • Contribution scope fit and linked maintainer decision: pending in [Design] Attaching to Chromium browsers that expose no HTTP discovery endpoint #2288. Passive, attach-only; no new launch, execution or mutation authority.
  • CLI and MCP/tool-catalog contract: additive. The six tools accept a second cdp_endpoint form. The MCP description and CLI endpoint help name it. The advertised JSON Schema type is unchanged (string).
  • Provider, bridge, target-format, or platform compatibility: Chromium-family browsers exposing a loopback browser socket. With a socket, browser.revision is Browser.getVersion's value (@<hash>) instead of WebKit-Version (537.36 (@<hash>)). attached is a boolean where Chrome's /json/list omits it and HTTP discovery reports null. Both are documented.
  • Evidence, artifact, provenance, or reconstruction contract: parameters.cdp_endpoint is the redacted socket URL for socket calls; unchanged for HTTP calls.
  • Target selection, authority, upstream behavior, and cleanup details: same target authorization and origin scoping as HTTP discovery. REA never closes the browser or a page; it closes only its socket.
  • Generated metadata, package, or installation impact: none. No skill change.

Expected effects:

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

The attach is the existing passive CDP attach, over a different loopback transport.

Evidence and regression coverage

  • Tests added or updated:

    • src/domain/browserObservation.test.ts: accepted socket forms, recorded redaction, and rejected shapes (implicit port, localhost, wss:, credentials, query, fragment, empty or nested id, page socket).
    • tests/boundary/browser/cdpBrowserProvider.browserSocket.test.ts: socket discovery matches HTTP discovery; inspection uses one socket and makes no HTTP requests; the socket closes on a missing target, malformed Browser.getVersion, and cancellation; a refused socket is endpoint_unreachable without echoing the id.
    • tests/boundary/cli/browserCli.test.ts, tests/boundary/mcp/browserMcp.test.ts: public CLI/MCP calls with a socket record the redacted endpoint. MCP reconciliation accepts that Evidence, and the advertised description names the socket form.
    • scripts/lib/browser-socket-e2e.mjs, wired into npm run verify:browser. On real Chrome it checks that DevToolsActivePort names the same socket as /json/version, and that socket discovery matches HTTP discovery. It also checks that a public CLI inspection through the socket records redacted Evidence and compares unchanged for DOM structure and scripts against an HTTP-discovery inspection.
    • The fake CDP browser answers Browser.getVersion with Chrome's reply shape and counts opened sockets.
  • Issue success check ([Design] Attaching to Chromium browsers that expose no HTTP discovery endpoint #2288, option 2), run manually on the notes-web demo (website/public/examples/notes-web/ served on 127.0.0.1). Chrome 138.0.7204.157 was started with an explicit --remote-debugging-port and a dedicated --user-data-dir. Only /json/version's webSocketDebuggerUrl was passed to the compiled CLI, and the result was compared with HTTP discovery:

    • list-browser-targets: the same target, title, URL and origin. The only difference is attached (null through HTTP, because Chrome's /json/list omits it; false through the socket).
    • inspect-web-page --include-script-sources --observation-ms 1000: identical apart from target.attached. Both returned the same three scripts (assets/main.js, assets/i18n.js, examples/notes-web/export.js) with identical source SHA-256 digests, 72 DOM nodes, an empty network.requests, and the same attach_limited_sections.
    • compare-web-captures (HTTP vs socket): dom_structure, scripts and resources are unchanged. network, metadata, webmcp, accessibility and storage are unknown, as for any two attach-window captures.
    • Recorded endpoints: http://127.0.0.1:<port> and ws://127.0.0.1:<port>/devtools/browser/[REDACTED]. The socket id appears in no CLI output.
  • Base reproduction: on upstream/main, rea list-browser-targets ws://127.0.0.1:PORT/devtools/browser/<id> fails input validation, because cdp_endpoint must be an explicit-port HTTP URL. Without HTTP discovery, no accepted endpoint exists.

  • User-visible CLI/MCP output: real Chrome 138, rea list-browser-targets ws://127.0.0.1:<port>/devtools/browser/<id> --allowed-origins http://127.0.0.1:<site> --json (trimmed):

    {
      "operation": "list_browser_targets",
      "parameters": {
        "cdp_endpoint": "ws://127.0.0.1:<port>/devtools/browser/[REDACTED]",
        "allowed_origins": ["http://127.0.0.1:<site>"]
      },
      "normalized_result": {
        "browser": {
          "product": "Chrome/138.0.7204.157",
          "protocol_version": "1.3",
          "revision": "@e533e98b1267baa1f1c46d666b120e64e5146aa9",
          "js_version": "13.8.258.26"
        },
        "targets": [
          {
            "type": "page",
            "title": "REA browser verifier",
            "url": "http://127.0.0.1:<site>/app",
            "origin": "http://127.0.0.1:<site>",
            "attached": false
          }
        ],
        "excluded": { "disallowed_origin": 0, "unsupported_url": 0, "non_page": 4 }
      }
    }

    The socket id does not appear anywhere in stdout.

  • Remaining proof gaps:

    • Not tested against the chrome://inspect toggle itself; the local Chrome is 138. The socket-only behavior and per-connection consent come from the third-party report above.
    • Other Chromium-family browsers (Brave, Edge) were not checked over the socket.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • Review fixes (head 616f8f84d): the browser-socket WebSocket handshake now runs inside the 10-second discovery deadline, and a reached deadline projects as provider_timeout rather than cancelled. New boundary tests cover a stalled discovery reply and a handshake that never completes. check:fast passes; tests/boundary/browser plus the browser CLI and MCP suites pass (45 files, 417 tests, 1 skipped).
  • Rebased on 4276aaf18 (head 4706bb9f4). The only conflict was with #951b095e1, which bounded HTTP discovery responses to 8 MiB and 10 seconds. Browser-socket discovery now shares that 10-second deadline (timeout reason, caller cancellation preserved, boundary test added). Each socket reply stays bounded by the 64 MiB protocol message limit. After the rebase: check:fast passes; all tests/boundary/browser files plus the browser/Electron CLI and MCP, Inspector and reconciliation suites pass (52 files, 467 tests, 1 skipped); the domain tests pass (6).
  • npm run check: typecheck, lint (0 errors), knip, metadata:check and architecture guards pass. format:check flagged only untracked local build output outside this diff; oxfmt --check on the 22 changed files passes.
  • npm run docs:check: passes.
  • npm run test:focused -- all tests/boundary/browser/*.test.ts, plus cli/browserCli, cli/electronCli, cli/browserRedirectShapeCompleteness, mcp/browserMcp, mcp/electronMcp, mcp/browserComparisonInput and inspector/javascriptRuntimeObservation: 48 files, 431 tests pass.
  • npm run test:focused -- the two JavaScript runtime reconciliation boundary suites: 13 tests pass.
  • npm run test:local -- 25 related browser, web, contract and Evidence source suites: 255 tests pass.
  • REA_BROWSER_EXECUTABLE=/usr/bin/google-chrome npm run verify:browser (Chrome 138.0.7204.157, headless, Linux): the new browser-socket step passes. The run then stops in the existing scenario launch-storage step (browser-scenario-storage-e2e.mjs: expected seeded storage values, got null). Unmodified upstream/main fails with the same assertion on this host, so the steps after it ran on neither revision. Run alone against the same Chrome, the browser-socket step returned {"targets":1,"revision":"@e533e98b1267baa1f1c46d666b120e64e5146aa9"}. Re-run after the capture-parity assertions were added: the step passes, and the lane stops at the same existing storage assertion.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Existing HTTP endpoints, results and Evidence are unchanged.
  • Real Hopper/Ghidra, browser, or OS coverage: real Google Chrome 138.0.7204.157 (headless) on Linux through npm run verify:browser.
  • Package or release metadata impact: none.
  • Agent and analysis cost: the MCP tools/list payload grows from 2,462,734 to 2,463,786 bytes (+1,052, about 0.04%), from the cdp_endpoint description on the six tools (14 schema occurrences). Tokens were not measured; at about 4 bytes per token that is roughly 260 tokens, an estimate. HTTP discovery calls run the same code path as before. Elapsed time, CPU and memory for the socket path were not benchmarked.
  • Security, privacy, process, or containment review: the socket id is treated as a transport credential and kept out of Evidence and errors. Loopback-only rules are unchanged. With the toggle, the user attaches to an existing profile, so the docs recommend --allowed-origins when listing targets. Chrome's own consent dialog still applies.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

Refs #2288

@all-black-493
all-black-493 marked this pull request as ready for review October 12, 2026 20:44
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@all-black-493
all-black-493 marked this pull request as draft October 12, 2026 20:45
@all-black-493
all-black-493 marked this pull request as ready for review October 12, 2026 20:48
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Repo admins can enable using credits for code reviews in their settings.

@all-black-493
all-black-493 force-pushed the feat/browser-websocket-endpoint branch from 948a0ab to fbac6ad Compare October 12, 2026 21:00
@all-black-493

all-black-493 commented Oct 12, 2026 •

Copy link
Copy Markdown
Author

Update (head fbac6ada4, rebased on 91b8f587c):

  • Ran the option-2 check from [Design] Attaching to Chromium browsers that expose no HTTP discovery endpoint #2288 on the notes-web demo. Passing only /json/version's webSocketDebuggerUrl gives the same targets and inspection as HTTP discovery, except attached: null through /json/list, false through Target.getTargets. compare-web-captures reports DOM structure, scripts and resources as unchanged. Details are in the description.
  • The real Chrome lane now also checks that DevToolsActivePort and /json/version name the same socket, and that socket and HTTP inspections compare unchanged for DOM structure and scripts.
  • On the new base, the browser-socket boundary, CLI and MCP suites (19 tests) and the domain tests (6) pass.

The scope decision in #2288 is still pending; this should not merge until it covers the final diff.

@all-black-493
all-black-493 marked this pull request as draft October 12, 2026 21:16
@all-black-493
all-black-493 force-pushed the feat/browser-websocket-endpoint branch from fbac6ad to e245af5 Compare October 12, 2026 21:22
@all-black-493

Copy link
Copy Markdown
Author

Rebased to resolve the conflict with #2091 (head e245af5de).

@morluto

morluto commented Oct 13, 2026

Copy link
Copy Markdown
Owner

@greptileai review

@greptile-apps

greptile-apps Bot commented Oct 13, 2026

Copy link
Copy Markdown
Contributor

This PR is set for a Plus review. TREX isn't supported with Plus or Apex reviews yet, so Greptile reviews this PR at Base while TREX is on.

@greptile-apps

greptile-apps Bot commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium impact] The reviewed code has no remaining blocking findings; the stated maintainer scope approval remains a separate merge prerequisite.

Summary

Adds browser-socket attachment to the six passive browser tools without HTTP discovery. Calls reuse one socket and record its endpoint with the browser identifier removed.

  • Passive browser tools can attach through a browser WebSocket.
  • Browser evidence hides the socket ID while keeping its endpoint.

Diagram

sequenceDiagram
  participant Caller
  participant REA
  participant Browser
  Caller->>REA: Passive tool with browser socket URL
  Note over REA,Browser: One deadline covers connection setup and discovery
  REA->>Browser: Open WebSocket
  REA->>Browser: Browser.getVersion
  REA->>Browser: Target.getTargets
  REA->>REA: Check selected target and allowed origin
  REA->>Browser: Target.attachToTarget on the same socket
  REA->>Browser: Collect requested observations
  REA->>Browser: Detach and close REA's socket
  REA-->>Caller: Evidence with socket identifier removed
Loading

Reviews (3) · Last reviewed commit: "fix(browser): bound browser-socket hands..." · Reviewed by Greptile

Accept a literal loopback browser WebSocket,
ws://127.0.0.1:PORT/devtools/browser/ID, as the cdp_endpoint of the six
passive browser tools. Discovery then reads Browser.getVersion and
Target.getTargets over that socket, and the target session adopts the
same socket, so a browser that asks for consent per connection prompts
once per call.

The socket identifier gates access, so Evidence records the endpoint as
ws://127.0.0.1:PORT/devtools/browser/[REDACTED], and runtime
reconciliation accepts that recorded form. HTTP endpoints and their
Evidence are unchanged. Electron, web runtime, scenario and Inspector
endpoints stay HTTP-only.

Refs morluto#2288
Check in the real Chrome lane that DevToolsActivePort names the same
browser socket as /json/version, and that inspecting the selected page
through that socket leaves DOM structure and scripts unchanged compared
with an inspection through HTTP discovery.

Refs morluto#2288
@all-black-493
all-black-493 force-pushed the feat/browser-websocket-endpoint branch from e245af5 to 4706bb9 Compare October 13, 2026 07:10
@all-black-493
all-black-493 marked this pull request as ready for review October 13, 2026 07:10
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-13T07:12:12.681626Z 4706bb9 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@all-black-493

Copy link
Copy Markdown
Author

Rebased on 4276aaf18 to resolve the conflict with #951b095e1 (head 4706bb9f4), and marked ready for review.

  • The conflict was in src/browser/CdpEndpoint.ts: #951b095e1 added CDP_DISCOVERY_RESPONSE_LIMITS (8 MiB, 10 s) to HTTP discovery, and this branch added the browser-socket schemas at the same place. Both are kept.
  • Browser-socket discovery bypasses readCdpJson, so it now applies the same 10-second deadline to its Browser.getVersion and Target.getTargets round trips. It fails with reason timeout, and caller cancellation still reports cancellation. A boundary test covers it with a 50 ms injected deadline. Each socket reply stays bounded by the 64 MiB protocol message limit. The docs mention the shared deadline.
  • After the rebase: check:fast passes, 52 browser/Electron/Inspector/CLI/MCP/reconciliation boundary files pass (467 tests, 1 skipped), and the domain tests pass.

The scope decision in #2288 is still open; this should not merge until it covers the final diff.

Comment thread src/browser/CdpTargetSession.ts Resolved
Comment thread src/browser/CdpEndpoint.ts Resolved
…eouts

Run the browser-socket WebSocket handshake and both discovery commands
under one discovery deadline, so a port that accepts the connection but
never completes the upgrade times out instead of waiting forever.

Report a reached deadline as a timeout without carrying the internal
abort as its cause, so CLI and MCP project provider_timeout rather than
cancelled. Caller cancellation is still reported as cancelled.

Refs morluto#2288
@all-black-493

Copy link
Copy Markdown
Author

Addressed Greptile's review in 616f8f84d; both threads are resolved with details.

  • Socket setup can wait forever: the WebSocket handshake and both discovery commands now share one 10-second discovery deadline.
  • Timeouts look like cancellations: a reached deadline no longer carries the internal abort as its cause, so it projects as provider_timeout.

New tests cover a stalled reply and a handshake that never completes, including the projected error code. check:fast passes, and the browser boundary, CLI and MCP suites pass (45 files, 417 tests, 1 skipped).

@morluto morluto added enhancement New feature or request area: runtime Process capture and runtime observation area: cli CLI adapter and one-shot commands area: mcp MCP server, transport, and tool registration P2: medium Medium priority issue labels Oct 13, 2026
@morluto
morluto merged commit 62c8b3d into morluto:main Oct 13, 2026
1 check passed
@morluto morluto mentioned this pull request Oct 14, 2026
3 tasks done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: cli CLI adapter and one-shot commands area: mcp MCP server, transport, and tool registration area: runtime Process capture and runtime observation enhancement New feature or request P2: medium Medium priority issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants