Skip to content

browser: route existing-session user profile through browser nodes#68891

Merged
mbelinky merged 5 commits intomainfrom
fix/browser-user-node
Apr 19, 2026
Merged

browser: route existing-session user profile through browser nodes#68891
mbelinky merged 5 commits intomainfrom
fix/browser-user-node

Conversation

@mbelinky
Copy link
Copy Markdown
Contributor

Summary

  • Problem: the browser tool hard-blocked profile="user" / existing-session when targeting a browser node, even though the docs describe node-host browser proxy as the default remote-gateway path.
  • Why it matters: remote gateways could drive the node-managed openclaw browser, but they could not reach the real signed-in Chrome session on the browser machine.
  • What changed: removed the existing-session node hard-block, kept sandbox rejection, preserved explicit target="host" pinning, and added focused regression coverage for omitted target, explicit target="node", and explicit target="host".
  • What did NOT change (scope boundary): this does not change sandbox behavior, does not broaden target="host" semantics, and does not touch Chrome MCP session management beyond allowing the browser-tool routing layer to use the existing node proxy path.

Change Type (select all)

  • Bug fix
  • Feature
  • Refactor required for the fix
  • Docs
  • Security hardening
  • Chore/infra

Scope (select all touched areas)

  • Gateway / orchestration
  • Skills / tool execution
  • Auth / tokens
  • Memory / storage
  • Integrations
  • API / contracts
  • UI / DX
  • CI/CD / infra

Linked Issue/PR

Root Cause (if applicable)

  • Root cause: extensions/browser/src/browser-tool.ts treated Chrome MCP existing-session profiles as host-local only and threw before node routing could run.
  • Missing detection / guardrail: there was no browser-tool test covering profile="user" with node routing semantics.
  • Contributing context (if known): the docs evolved toward node-host browser proxy as the remote-gateway story, but the tool routing guard still reflected the older local-only assumption.

Regression Test Plan (if applicable)

  • Coverage level that should have caught this:
    • Unit test
    • Seam / integration test
    • End-to-end test
    • Existing coverage already sufficient
  • Target test or file: extensions/browser/src/browser-tool.test.ts
  • Scenario the test should lock in: profile="user" should auto-route through a connected browser node when target is omitted, should work with explicit target="node", and should still stay on host when target="host" is explicit.
  • Why this is the smallest reliable guardrail: the bug sits in browser-tool target resolution before any live browser I/O, so the focused tool test covers the decision logic directly without requiring live Chrome.
  • Existing test that already covers this (if any): none for the node-routing path.
  • If no new test is added, why not: N/A.

User-visible / Behavior Changes

  • Remote gateways can now use profile="user" through a connected browser node instead of being hard-blocked.
  • Explicit target="host" still pins the host and prevents node auto-routing.

Diagram (if applicable)

Before:
remote gateway + browser node + profile="user" -> browser tool hard-block -> failure

After:
remote gateway + browser node + profile="user" -> node proxy routing -> existing-session browser on node

Security Impact (required)

  • New permissions/capabilities? (No)
  • Secrets/tokens handling changed? (No)
  • New/changed network calls? (No)
  • Command/tool execution surface changed? (No)
  • Data access scope changed? (No)
  • If any Yes, explain risk + mitigation:
    • N/A

Repro + Verification

Environment

  • OS: macOS local dev host for focused tests; bug originally reproduced on a remote gateway + browser-node setup
  • Runtime/container: Node.js workspace via pnpm
  • Model/provider: N/A
  • Integration/channel (if any): browser tool
  • Relevant config (redacted): browser-capable node connected; browser.profiles.user.driver = "existing-session"

Steps

  1. Connect a browser-capable node.
  2. Invoke the browser tool with profile="user" and either no target or target="node".
  3. Verify routing uses the node proxy instead of throwing.

Expected

  • profile="user" can route through the connected browser node.
  • Explicit target="host" still stays on host.

Actual

  • Matches expected with the new routing logic and focused tests.

Evidence

  • Failing test/log before + passing after
  • Trace/log snippets
  • Screenshot/recording
  • Perf numbers (if relevant)

Human Verification (required)

What you personally verified (not just CI), and how:

  • Verified scenarios: focused browser-tool test file passes from a clean worktree; omitted target and explicit target="node" both route through the node proxy; explicit target="host" remains host-pinned.
  • Edge cases checked: profile="user" still rejects target="sandbox" and still falls back to host when no browser node is available.
  • What you did not verify: a fresh live Chrome attach end-to-end from this clean worktree after pushing the branch.

Review Conversations

  • I replied to or resolved every bot review conversation I addressed in this PR.
  • I left unresolved only the conversations that still need reviewer or maintainer judgment.

Compatibility / Migration

  • Backward compatible? (Yes)
  • Config/env changes? (No)
  • Migration needed? (No)
  • If yes, exact upgrade steps:
    • N/A

Risks and Mitigations

  • Risk: remote callers that intentionally depended on the old hard-block could now reach the node path.
    • Mitigation: that path was already documented as the remote-browser architecture, and explicit target="host" pinning still preserves the old host-only intent when needed.
  • Risk: future refactors could accidentally re-break the host pinning contract while fixing node routing.
    • Mitigation: the new focused tests lock both behaviors in the same file.

@openclaw-barnacle openclaw-barnacle Bot added size: S maintainer Maintainer-authored PR labels Apr 19, 2026
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 19, 2026

Greptile Summary

This PR fixes a bug where the browser tool unconditionally hard-blocked profile="user" (existing-session) when a browser node was connected, preventing remote gateways from reaching the signed-in Chrome session on the browser node as documented. The fix removes the blanket node guard, retains the sandbox rejection and explicit target="host" pinning, and adds a post-resolution fallback to "host" when no node is available. Three focused regression tests lock in omitted-target auto-routing, explicit target="node", and explicit target="host" pinning.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped, the fallback-to-host logic is correct, sandbox rejection is preserved, and focused tests cover all three relevant routing branches.

All findings are P2 or lower. The routing logic is correct, the sandbox guard and explicit host-pin are preserved, and the new tests close the coverage gap that originally allowed this bug to exist.

No files require special attention.

Reviews (1): Last reviewed commit: "browser: route user profile through brow..." | Re-trigger Greptile

@openclaw-barnacle openclaw-barnacle Bot added docs Improvements or additions to documentation gateway Gateway runtime labels Apr 19, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 06b641efb1

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/browser/src/browser-tool.ts Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d6ada08a5c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread extensions/browser/src/browser-tool.ts
Copy link
Copy Markdown
Contributor Author

Follow-up on the remaining security-bot concern: I do not think it blocks this PR.

This patch is aligning existing-session (profile=\"user\") with the browser-node routing model that the docs already describe for remote gateways; it is not inventing a new node-target concept. The explicit contracts still hold:

  • target=\"host\" stays pinned to host
  • target=\"node\" / node=<id> remain explicit node routing
  • target=\"sandbox\" is still rejected for existing-session
  • omitted target still falls back to host when node routing is unavailable

The sandbox-policy point is broader than this PR: today allowHostControl is documented and implemented as a host-target gate, not a generic non-sandbox browser gate, and node browser routing already exists for the managed browser path. If we want a stronger trust model around node browser delegation, that should be handled as a separate policy design change rather than by keeping this existing-session bug in place.

@aisle-research-bot
Copy link
Copy Markdown

aisle-research-bot Bot commented Apr 19, 2026

🔒 Aisle Security Analysis

We found 1 potential security issue(s) in this PR:

# Severity Title
1 🟠 High Automatic routing of existing-session (profile=user) to browser nodes may expose node-local browser data without explicit opt-in
1. 🟠 Automatic routing of existing-session (profile=user) to browser nodes may expose node-local browser data without explicit opt-in
Property Value
Severity High
CWE CWE-862
Location extensions/browser/src/browser-tool.ts:403-463

Description

The browser tool now allows existing-session profiles (e.g. profile="user") to be routed through a connected browser node proxy when available.

This introduces a security risk in deployments where:

  • tool callers are not fully trusted (e.g., untrusted prompts/users can invoke the tool), and
  • browser nodes run under real user accounts with existing Chrome/Chromium profiles.

Because existing-session attachment uses Chrome DevTools MCP and may be configured with userDataDir, routing to a node means the tool can attach to and control a node-host browser profile (cookies/logins/history) and potentially select a sensitive local profile directory on that node.

Key issue:

  • The tool will attempt resolveBrowserNodeTarget(...) even for existing-session profiles, and when a node is found it will send the request to node.invoke / browser.proxy.
  • There is no requirement for an explicit target="node" or explicit node pin (node=<id>) when using profile="user".

Vulnerable behavior (new/changed logic):

nodeTarget = await resolveBrowserNodeTarget({ requestedNode, target, sandboxBridgeUrl });
...
const proxyRequest = nodeTarget ? async (...) => callBrowserProxy({ nodeId: nodeTarget.nodeId, ... }) : null;

This can unexpectedly move an existing-session attachment from the local host to a node, expanding the attack surface to the node's filesystem/profile context.

Recommendation

Require explicit opt-in before routing existing-session profiles to a browser node.

Options:

  1. Conservative default: keep existing-session profiles on host unless target="node" or node=<id|name> is explicitly provided.
  2. Add a config gate such as gateway.nodes.browser.allowExistingSessionOnNode: true (default false).
  3. Enforce node-side allowlisting for sensitive profiles (e.g., profile=user) by default.

Example (option 1):

if (isUserBrowserProfile && !requestedNode && target !== "node") {
  target = "host";
}

This ensures node-hosted existing-session attachments cannot occur implicitly and prevents accidental exposure of node-local browser profiles.


Analyzed PR: #68891 at commit 9b50845

Last updated on: 2026-04-19T10:20:55Z

@mbelinky mbelinky force-pushed the fix/browser-user-node branch from 9def88f to 23fa1d1 Compare April 19, 2026 10:10
@mbelinky mbelinky merged commit 8cb7384 into main Apr 19, 2026
48 checks passed
@mbelinky mbelinky deleted the fix/browser-user-node branch April 19, 2026 10:21
jinon86 pushed a commit to jinon86/openclaw that referenced this pull request Apr 19, 2026
* test: stabilize standalone Parallels smoke lanes

* fix: strip orphaned OpenAI reasoning blocks before responses API call (openclaw#55787)

Merged via squash.

Prepared head SHA: 263b952
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Reviewed-by: @jalehman

* fix: stabilize release smoke reruns

* test: tolerate empty fireworks live responses

* test: make OpenWebUI smoke deterministic

* tasks: extract detached task lifecycle runtime (openclaw#68886)

* tasks: extract detached task lifecycle runtime

* tests: relax gateway seam expectation

---------

Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>

* test: complete workspace setup in update smokes

* fix: parse PowerShell cron tools allow-list (openclaw#68858) (thanks @chen-zhang-cs-code)

* fix(cron): parse PowerShell tools allow list

* fix(cron): clarify tools allow-list help

* fix: parse PowerShell cron tools allow-list (openclaw#68858) (thanks @chen-zhang-cs-code)

---------

Co-authored-by: Ayaan Zaidi <hi@obviy.us>

* fix(browser): discover CDP websocket from bare ws:// URL before attach (openclaw#68715)

* fix(browser): discover CDP websocket from bare ws:// URL before attach

When browser.cdpUrl is set to a bare ws://host:port (no /devtools/ path), ensureBrowserAvailable would call isChromeReachable -> canOpenWebSocket against the URL verbatim. Chrome only accepts WebSocket upgrades at the specific path returned by /json/version, so the handshake failed immediately with HTTP 400. With attachOnly: true, that surfaced as:

  Browser attachOnly is enabled and profile "openclaw" is not running.

even though the CDP endpoint was reachable and the profile was healthy. Reproduced by the new tests in chrome.test.ts and cdp.test.ts (openclaw#68027).

Fix: introduce isDirectCdpWebSocketEndpoint(url) — true only when a ws/wss URL has a /devtools/<kind>/<id> handshake path. Route any other ws/wss cdpUrl (including the bare ws://host:port shape) through HTTP /json/version discovery by normalising the scheme via the existing normalizeCdpHttpBaseForJsonEndpoints helper. Apply this in isChromeReachable, getChromeWebSocketUrl, and createTargetViaCdp. Direct WS endpoints with a /devtools/ path are still opened without an extra discovery round-trip.

Fixes openclaw#68027

* test(browser): add seeded fuzz coverage for CDP URL helpers

Adds property-based / seeded-fuzz tests for the URL helpers the
attachOnly CDP fix depends on (openclaw#68027):

  - isWebSocketUrl
  - isDirectCdpWebSocketEndpoint
  - normalizeCdpHttpBaseForJsonEndpoints
  - parseBrowserHttpUrl
  - redactCdpUrl
  - appendCdpPath
  - getHeadersWithAuth

Follows the existing repo convention (see
src/gateway/http-common.fuzz.test.ts): no fast-check dep, small
mulberry32 PRNG + hand-rolled generators, deterministic per-describe
seeds so failures are reproducible.

Lifts cdp.helpers.ts coverage from 77.77% -> 89.54% statements,
67.9% -> 80.24% branches, 78% -> 90% lines. Remaining uncovered
lines are inside the WS sender internals (createCdpSender,
withCdpSocket, fetchCdpChecked rate-limit branch), which require
integration-style mocks and are unrelated to the attachOnly fix.

* test(browser): drive cdp.helpers/cdp/chrome to 100% coverage

Lifts the three files touched by the openclaw#68027 attachOnly fix to 100% statements/branches/functions/lines across the extensions test suite. Adds cdp.helpers.internal.test.ts, cdp.internal.test.ts, and chrome.internal.test.ts covering error paths, branch matrices, CDP session helpers, Chrome spawn/launch/stop flows, and canRunCdpHealthCommand. Defensively unreachable guards are annotated with c8 ignore + inline justifications.

* fix(browser): restore WS fallback for non-/devtools ws:// CDP URLs

When /json/version discovery is unavailable (or returns no
webSocketDebuggerUrl), fall back to treating the original bare ws/wss
URL as a direct WebSocket endpoint. This preserves the openclaw#68027 fix for
Chrome's debug port while restoring compatibility with Browserless/
Browserbase-style providers that expose a direct WebSocket root without
a /json/version endpoint.

Priority order for bare ws/wss cdpUrl inputs:
  1. /devtools/<kind>/<id> URL \u2192 direct handshake, no discovery (unchanged)
  2. bare ws/wss root \u2192 try HTTP discovery first; if discovery returns a
     webSocketDebuggerUrl use it; otherwise fall back to the original URL
     as a direct WS endpoint
  3. HTTP/HTTPS URL \u2192 HTTP discovery only, no fallback (unchanged)

Affected call sites: isChromeReachable, getChromeWebSocketUrl,
createTargetViaCdp.

Also renames a misleading test ('still enforces SSRF policy for direct
WebSocket URLs') to accurately describe what it tests: SSRF enforcement
on the navigation target URL, not on the CDP endpoint.

New tests added for all three fallback paths. Coverage remains 100% on
all three touched files (238 tests).

* fix: browser attachOnly bare ws CDP follow-ups (openclaw#68715) (thanks @visionik)

* test(tasks): align detached runtime mock return types

* browser: route existing-session user profile through browser nodes (openclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test

* fix: default kimi thinking to off (openclaw#68907)

Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>

* docs(changelog): note kimi thinking default fix

* docs(changelog): add thanks for kimi fix

* tasks: add detached runtime plugin registration contract (openclaw#68915)

* tasks: register detached runtime plugins

* tasks: harden detached runtime ownership

* tasks: extract detached runtime contract types

* changelog: note detached runtime contract

* changelog: attribute detached runtime contract

* feat(ui): add a2a operator dashboard shell

* fix(ui): align a2a baseline with current read contract

* feat(ui): add A2A case inspector panels

* CI: route small workflows to ubuntu-latest

* CI: route fork-blocked workflows to GitHub-hosted runners

* CI: fall back to GITHUB_TOKEN for fork automation

* CI: skip app-token steps when fork secrets are absent

* CI: gate fork app-token steps through env guards

* CI: fix stale fallback env guard

* fix(ui): sync A2A locale snapshots

* fix(ui): sync A2A locale metadata

---------

Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Subash Natarajan <suboss87@gmail.com>
Co-authored-by: suboss87 <11032439+suboss87@users.noreply.github.com>
Co-authored-by: jalehman <550978+jalehman@users.noreply.github.com>
Co-authored-by: Mariano <132747814+mbelinky@users.noreply.github.com>
Co-authored-by: Mariano Belinky <mariano@mb-server-643.local>
Co-authored-by: ZC <chenzhangcode@163.com>
Co-authored-by: Ayaan Zaidi <hi@obviy.us>
Co-authored-by: Viz <visionik@pobox.com>
Co-authored-by: Frank Yang <frank.ekn@gmail.com>
Co-authored-by: termtek <termtek@ubuntu.tail2b72cd.ts.net>
Co-authored-by: bangtong-ai <bangtong-ai@users.noreply.github.com>
Co-authored-by: OpenClaw Agent <openclaw-agent@users.noreply.github.com>
Co-authored-by: OpenClaw Bot <bot@openclaw.local>
Mquarmoc pushed a commit to Mquarmoc/openclaw that referenced this pull request Apr 20, 2026
…penclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test
lovewanwan pushed a commit to lovewanwan/openclaw that referenced this pull request Apr 28, 2026
…penclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test
ogt-redknie pushed a commit to ogt-redknie/OPENX that referenced this pull request May 2, 2026
…penclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test
github-actions Bot pushed a commit to Desicool/openclaw that referenced this pull request May 9, 2026
…penclaw#68891)

* browser: route user profile through browser nodes

* browser: align existing-session node docs

* browser: preserve host fallback on node discovery errors

* browser: preserve configured node pin errors

* browser: widen config mock in node pin test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

docs Improvements or additions to documentation gateway Gateway runtime maintainer Maintainer-authored PR size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: profile="user" / existing-session Chrome MCP is hard-blocked for target="node" despite node-host browser docs

1 participant