Skip to content

fix(browser): keep browser.proxy off the process proxy - #1628

Merged
bkudiess merged 16 commits into
openclaw:mainfrom
SebTardif:fix/f009-browser-proxy-direct
Oct 20, 2026
Merged

bkudiess merged 16 commits into
openclaw:mainfrom
SebTardif:fix/f009-browser-proxy-direct

Conversation

@SebTardif

@SebTardif SebTardif commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

What Problem This Solves

browser.proxy talks to 127.0.0.1, but its HTTP client followed the process proxy. With HTTP_PROXY or ALL_PROXY set, the gateway bearer token was sent to that proxy.

User Impact

Browser control calls go straight to the local control port. Response size and redirects are unchanged from the previous client.

Why This Change Was Made

new HttpClient() uses the default proxy, and current .NET does not skip loopback unless NO_PROXY says so. A 1 MiB response cap and a redirect ban were tried and then removed, because they change existing control-host behavior and need a maintainer decision.

Evidence

Head f9c993c35202b35ed90dde4aaa0189199006395f. HTTP_PROXY and ALL_PROXY pointed at a local listener on 79259c3c. The production client, with no substitute handler, called a second listener on the control port. The call succeeded and the proxy accepted 0 connections. That client code is unchanged on this head.

browser-proxy direct gateway_port=56139 control_port=56141 proxy_port=56140 proxy_connections=0 ok=True

GatewayFixtureHost --browser-proxy started the built tray against a loopback fixture gateway that accepts the node role, with a control host on the gateway port plus 2. winnode --command browser.proxy --params {"path":"/tabs"} exited 0.

{"result":{"ok":true}}
browser-control requests=1 authorized=1

A gateway nodes invoke through the installed WSL gateway was not run.

On head 0b29606d23a41615caa2bf0ac8c19eaeffcfdb64, a live child with OPENCLAW_GATEWAY_FIXTURE=1 called FixtureLoopbackListenerOwner.IsOwnedByCurrentProcessParent, then BrowserProxyCapability.ExecuteAsync. The authorize callback was that owner method, not a stub that always denies. The bearer value was a local marker, not a gateway token. The assemblies were built from this worktree. The on-disk tray executable is older than this head, so this run did not launch that executable.

A different process listened on 127.0.0.1:63773. The child's parent was alive and was not that listener. The owner returned false. browser.proxy returned the owner block, and the listener accepted 0 connections.

The same pattern then ran while the parent process owned 127.0.0.1:63774. The listener pid and start time matched the parent, and the owner returned true. After that parent left the process table, a different process listened on 127.0.0.1:63775. The child could no longer open the old parent id. The owner returned false, browser.proxy returned the same block, and the replacement listener accepted 0 connections.

foreign port=63773 listener=37216 parent=4604 child=41292 fixture=True owned=False denyOk=False accepts=0
owned port=63774 listener=42372 parent=42372 child=42428 fixture=True owned=True
expired parent=42372 lookup=failed replacement=63775 listener=18680 owned=False denyOk=False accepts=0
denyError=Browser control authentication was blocked because the local listener owner could not be verified.

Change Type

  • Bug fix
  • Feature
  • Refactor
  • Docs or instructions
  • Tests or validation
  • Security hardening
  • Chore or infrastructure

Scope

  • Tray or WinUI UX
  • Windows node capability
  • Local MCP or winnode
  • Gateway, connection, or pairing
  • Setup or onboarding
  • Permissions, privacy, or security
  • Tests, CI, or docs

Required proof pools

  • none: no tray page, installer, or WSL gateway path changed. The proof is a loopback control listener and a loopback proxy listener.

Validation

Head af5ad2d363fae7fff4ed58b476ba21dcf4714035 in worktree C:\Users\sebta\wt-wn-1628. This head merges origin/main 94006b3672a034983e80a78dafe311e24ba80cce, ignores a fixture send lock that the connection handler already disposed, and keeps the audio startup test block longer than the test's observation wait. On bd8b2d0b32149fd46f51095e09329d8e4ee80e17, CI failed PublishedVerifierEnforcesRecoveryAndModelPhaseTokens(blockModel: True) with ObjectDisposedException from SemaphoreSlim.Release in FixtureGatewayServer.CloseConnectionsAsync. On 03e039d8a6c39c25c7fc048e2bc7059e40ca1d03, the UI job failed StopDuringStartup_WaitsForInitializationThenCleansUp: Assert.False(stopTask.IsCompleted) saw True because the fake start block timed out at 2 seconds. OPENCLAW_REPO_ROOT was that worktree.

  • ./build.ps1: exit 0
  • dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore: 4267 passed, 33 skipped, 0 failed, total 4300
  • dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore: 3996 passed, 0 failed, total 3996
  • dotnet test ./tests/OpenClaw.WinNode.Cli.Tests/OpenClaw.WinNode.Cli.Tests.csproj --no-restore: 129 passed, 0 failed, total 129
  • PublishedVerifierEnforcesRecoveryAndModelPhaseTokens: 2 passed
  • CloseConnectionsAsync_ToleratesAConnectionHandlerThatDisposedItsSendLock: passed
  • AudioPipelineFirstAudioTimeoutTests on af5ad2d363fae7fff4ed58b476ba21dcf4714035: 13 passed, 0 failed. The product build, Shared, and Tray counts above are from 03e039d8a6c39c25c7fc048e2bc7059e40ca1d03. This commit does not change those projects.

The fixture host keeps the browser-proxy control listener and also accepts main's session-mutation scenario and setup handoff. The merge does not change browser.proxy production code. GatewayFixtureIsolation now requires the fixture local data directory to match the legacy app-data root plus OpenClawTray.

On the earlier head f9c993c35202b35ed90dde4aaa0189199006395f: Connection FixtureLoopbackListenerOwnerTests 6 passed, RealNodeClient_FixtureAcceptsTheNodeRole 1 passed, and BrowserProxyProfileKeepsExecutionCapabilitiesOff 1 passed. RealNodeClient_FixtureAcceptsTheNodeRole is part of the Shared suite that passed on this head.

Real behavior proof

  • Behavior or issue addressed: browser.proxy could send the gateway bearer token to the process HTTP proxy.
  • Real environment tested: Windows 11, worktree C:\Users\sebta\wt-wn-f009, head f9c993c35202b35ed90dde4aaa0189199006395f.
  • Exact steps or command run after this patch: HTTP_PROXY and ALL_PROXY pointed at a TcpListener on 127.0.0.1 on 79259c3c. On this head, GatewayFixtureHost --browser-proxy launched the built tray. winnode --list-tools included browser.proxy. winnode --command browser.proxy --params {"path":"/tabs"} called the fixture control host.
  • Evidence after fix:
browser-proxy direct gateway_port=56139 control_port=56141 proxy_port=56140 proxy_connections=0 ok=True
{"result":{"ok":true}}
browser-control requests=1 authorized=1

The owner check rejects an unrelated listener, a parent id reused by a later process, a listener whose start time does not match the live parent, and an exited parent. BrowserProxy_UnverifiedListener_DoesNotSendTheRequest denies authorization and leaves the HTTP handler unused.

  • Observed result after fix: The direct control call did not touch the process proxy. The MCP call returned ok: true. The fixture host owned the control port and recorded one authorized request. The token was not printed. A denied owner check does not send a request.
    On head 0b29606d23a41615caa2bf0ac8c19eaeffcfdb64, the live child above is the current-head owner trace. A foreign loopback listener was rejected before HTTP (accepts=0). A live parent that owned the listener was accepted (owned=True). After that parent exited, the replacement listener was rejected before HTTP (accepts=0).

  • What was not tested: A gateway nodes invoke through the installed WSL gateway was not run. The tray browser page was not opened. The direct-proxy listener proof was collected on 79259c3c. That client code is unchanged on this head. This session did not relaunch the built tray. Exact parent-id reuse was not forced. The expired case is the parent process disappearing while another process owns the port. The live owner trace above was collected on 0b29606d23a41615caa2bf0ac8c19eaeffcfdb64.

  • Screenshot or artifact links verified? N/A

Security Impact

  • New permissions or capabilities? No
  • Secrets or tokens handling changed? Yes
  • New or changed network calls? No
  • Command or tool execution surface changed? No
  • Data access scope changed? No
  • If any answer is Yes, explain the risk and mitigation: The gateway bearer token is still sent to the loopback browser-control port. It is no longer sent through HTTP_PROXY or ALL_PROXY.

Compatibility and Migration

  • Backward compatible? Yes
  • Config or environment changes? No
  • Migration needed? No

Review Conversations

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

On head f9c993c, GatewayFixtureHost --browser-proxy-foreign bound port 53646 in a child process. The tray parent was the host. winnode --command browser.proxy returned exit 1: Browser control authentication was blocked because the local listener owner could not be verified. The child count file stayed at requests=0, so no socket was accepted and no token was sent. The installed WSL gateway service last exited 78, and doctor --fix is not used, so a nodes invoke on that gateway was not run.

The loopback browser-control client no longer follows HTTP_PROXY.
Each control response is read only up to 1 MiB, and a larger body is
rejected instead of being copied into the gateway error.

Validation: ./build.ps1 exit 0. Shared 4227 passed, 33 skipped. Tray
3893 passed.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper

clawsweeper Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

🦞👀
ClawSweeper picked this up.

Pull request received. I will update this pull request when review starts.

ClawSweeper review complete

ClawSweeper finished reviewing this revision. The review result is being finalized.

View the workflow run.

@clawsweeper clawsweeper Bot added P2 Normal priority bug or improvement with limited blast radius. merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. labels Oct 4, 2026
@clawsweeper

clawsweeper Bot commented Oct 4, 2026 •

Copy link
Copy Markdown

Codex review: needs changes before merge.

What this changes

This PR bypasses the process proxy for browser-control requests and adds isolated fixture coverage.

Example: HTTP_PROXY points to 127.0.0.1:56140 while browser control listens on 127.0.0.1:56141.

  • Before: The default HTTP client can send the bearer-bearing browser-control request through the proxy.
  • After: The production client reaches the control listener directly, with zero proxy connections.

Review scores

Measure Result What it means
Overall readiness 🐚 platinum hermit (4/6) The focused production fix has applicable real transport and MCP proof; the latest regression hardening lacks reported execution, while merge conflicts remain a separate workflow blocker.
Proof confidence 🦞 diamond lobster (5/6) Sufficient (live_output): Supplied Windows live output exercises the real HTTP client with zero proxy connections and the built tray through winnode discovery and invocation; foreign and expired-parent listeners receive no request, and the proved production client and owner-check blobs remain unchanged.
Patch quality 🐚 platinum hermit (4/6) No actionable review findings were identified.

Product

Kind: Bug fix · Worth it: Yes · Fix scope: Complete
User problem: Browser-control requests can expose the shared gateway token to the configured process HTTP proxy.
Reason: Direct loopback routing restores the local credential boundary; the added flags select developer fixture scenarios rather than new installed-app policy.

Merge readiness

⛔ Needs changes before merge - 1 item remains

Keep open: current main still needs the direct browser-control transport fix, and no discrete introduced defect was found; GitHub reports a merge conflict that blocks landing.

Priority: P2
Reviewed head: c2ac9503f1185f8f9b68a50425504c4b032c52c5

Before merge

  • Complete next step - Resolve GitHub's reported merge conflict, then validate the resulting head with the required build, Shared, Tray, and CLI suites plus the focused proxy and fixture-startup regressions.

Findings

None.

Tests

  • Missing end-to-end proof: Execution demonstrating base failure and head success for the newly hardened proxy and startup-cleanup regressions is not supplied.
Agent review details

How this fits together

The shared browser capability receives node or MCP commands, authorizes the local browser-control endpoint, sends authenticated HTTP requests, and returns browser results.

flowchart LR
  A[Gateway node command] --> C[Browser capability]
  B[Local MCP command] --> C
  C --> D[Endpoint authorization]
  D --> E[Direct HTTP client]
  E --> F[Loopback browser control]
Loading

Technical review

Best possible solution:

Land the direct transport fix with the existing endpoint authorization and fixture isolation preserved.

Do we have a high-confidence way to reproduce the issue?

Current-main source constructs a proxy-aware client and attaches the bearer token; supplied Windows transport observations support the reported path, but this review executed no reproduction.

Is this the best way to solve the issue?

Disabling proxy use in the shared browser-control client addresses the cause directly, and the additional fixture switches exercise the existing command without introducing a product setting.

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning medium; reviewed against 69276a1bcca5.

Provenance checked

Testing

Proof path: shipped entry point.

Security

None.

Evidence

What I checked:

Likely related people:

  • Scott Hanselman: Raw commit ff92351 adds src/OpenClaw.Shared/Capabilities/BrowserProxyCapability.cs:37 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: ff923514afa1; files: src/OpenClaw.Shared/Capabilities/BrowserProxyCapability.cs)
  • Barbara Kudiess: Raw commit bc210e0 adds src/OpenClaw.Tray.WinUI/App.xaml.cs:2245 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: high; commits: bc210e048aa9; files: src/OpenClaw.Tray.WinUI/App.xaml.cs)
  • anagnorisis2peripeteia: Raw commit a2e2773 adds src/OpenClaw.Shared/Capabilities/BrowserProxyCapability.cs:34 relative to its recorded parents. This identifies author metadata, not feature responsibility or a PR merger. (role: source-line author; confidence: medium; commits: a2e2773bb935; files: src/OpenClaw.Shared/Capabilities/BrowserProxyCapability.cs)

Labels

Label changes:

  • add status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.
  • remove status: 👀 ready for maintainer look: Current PR status label is status: ⏳ waiting on author.

Label justifications:

  • P2: This fixes a concrete credential-routing defect when a process proxy is configured, without evidence of an urgent deployment incident.
  • rating: 🐚 platinum hermit: Overall readiness is 🐚 platinum hermit; proof is 🦞 diamond lobster and patch quality is 🐚 platinum hermit.
  • status: ⏳ waiting on author: ClawSweeper has contributor-facing work open and is waiting for author action.
  • proof: sufficient: Contributor real behavior proof is sufficient.

Rating scale

6/6 🦀 challenger crab · 5/6 🦞 diamond lobster · 4/6 🐚 platinum hermit · 3/6 🦐 gold shrimp · 2/6 🦪 silver shellfish · 1/6 🧂 unranked krab. Overall follows the weaker of proof and patch quality; ✨ marks media proof (a screenshot, video, or linked artifact) that directly shows the changed behavior.

Workflow

ClawSweeper edits this one comment on every review. Comment @clawsweeper re-review for a fresh review only. Repair and merge execution lanes are retired.

History

Review history (16 earlier review cycles; latest 8 shown)
  • reviewed 2026-10-05T18:11:23.698Z sha 8d29baa :: needs real behavior proof before merge. :: none
  • reviewed 2026-10-05T18:28:41.182Z sha 0b29606 :: needs real behavior proof before merge. :: none
  • reviewed 2026-10-06T06:34:12.928Z sha 0b29606 :: needs changes before merge. :: none
  • reviewed 2026-10-07T09:06:34.005Z sha 0b29606 :: needs changes before merge. :: none
  • reviewed 2026-10-07T09:58:02.655Z sha bd8b2d0 :: needs maintainer review before merge. :: none
  • reviewed 2026-10-07T10:28:12.069Z sha 03e039d :: needs maintainer review before merge. :: none
  • reviewed 2026-10-07T10:59:32.989Z sha af5ad2d :: needs maintainer review before merge. :: none
  • reviewed 2026-10-08T15:39:04.132Z sha 027996f :: needs maintainer review before merge. :: none

Reviewed October 20, 2026, 2:12 AM ET / 06:12 UTC (Revision 17).

Drop the 1 MiB response cap and the redirect ban. Those change
existing control-host behavior and need a maintainer decision.
The loopback client still ignores HTTP_PROXY and ALL_PROXY.

Validation: ./build.ps1 exit 0. Shared 4226 passed, 33 skipped. Tray
3893 passed.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot removed the merge-risk: 🚨 compatibility 🚨 Merging this PR could break existing users, config, migrations, defaults, or upgrades. label Oct 4, 2026
MCP-only mode listens without a gateway client, so browser.proxy is
absent until the toggle, gateway client, shared token, and verified
browser endpoint are all present. Connection refused on port 8765
means the tray is not listening.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@SebTardif SebTardif changed the title fix(browser): keep browser.proxy off the process proxy and cap the body fix(browser): keep browser.proxy off the process proxy Oct 4, 2026
The browse fixture accepts the node role. --browser-proxy enables node
mode and browser.proxy, and serves a loopback control host on the
gateway port plus 2. The tray sends the shared token only when the
process that launched the fixture app owns that port.

Against that host, winnode --command browser.proxy returned
{"result":{"ok":true}} for path /tabs. The control log was
browser-control requests=1 authorized=1.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added the merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. label Oct 4, 2026
A parent process id can be reused after the fixture launcher exits. The
check now keeps that process handle and requires its start time to be
at or before the tray, and equal to the listener start time. An exited
parent, a later replacement, or a different listener is rejected before
any HTTP request.

Tests cover the unrelated listener, the reused id, the reassigned start
time, and a denied authorizer that leaves the HTTP handler unused.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
--browser-proxy-foreign binds the control port in a child process.
The tray parent is the host, so the owner check refuses the call
before any socket accept. A live run returned the authorization error
and the child count file stayed at requests=0.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
The count file was written before TcpListener.Start, so the host
could treat a port that was still closed as ready. The file is now
written after the bind succeeds. A taken port exits without creating
the file.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Core failed a markdown list timing budget: 30k items took 570ms against a 500ms limit.
Network recovery failed in wsl-create before the tests: the fresh distro returned no root output.
The bind-order change does not parse markdown or create a WSL distro.
Tray, UI, Setup, and Revocation passed on this head.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
The count file name was only the control port, and any existing file
counted as ready. A leftover marker from an earlier run could be
accepted before this child bound the port. Each attempt now uses a
new file and deletes it when the listener stops.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Core failed McpHttpServerTests.Dispose_DuringInFlightHandler with ObjectDisposedException on a file handle.
Network recovery failed in wsl-create: the fresh distro was not registered.
Neither path is the foreign-listener readiness file.
Tray, UI, Setup, and Revocation passed on this head.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@clawsweeper clawsweeper Bot added proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. and removed status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. rating: 🦐 gold shrimp Decent PR readiness signal, but merge confidence is limited. merge-risk: 🚨 security-boundary 🚨 Merging this PR could weaken sandboxing, authorization, credentials, or sensitive data. labels Oct 6, 2026
Keep the browser-proxy foreign control listener, and take main's session-mutation scenario and setup handoff on the fixture host.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
CloseConnectionsAsync can snapshot a connection whose handler then disposes the send semaphore. A restart treats that connection as already closed.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
The fake start and stop blocks timed out at 2 seconds, the same bound the test uses to notice that recording has started. On a busy runner the block expired first and StopAsync finished before the assertion.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
Keep main's device-token check on the fixture gateway and this
branch's node role return. A paired device token is still judged
alone. A shared token still has to match. Operator and node both
complete hello.

Signed-off-by: Sebastien Tardif <SebTardif@ncf.ca>
@karkarl

karkarl commented Oct 9, 2026

Copy link
Copy Markdown
Collaborator

Global triage: HOLD_FOR_AUTHOR. Take confidence 20%; recommendation confidence 98%; effort medium; risk high.

Reviewed exact head 027996faa892. GitHub currently reports CLEAN with 24 passing, 0 failing, and 0 pending checks. proof label is sufficient. Durable automated signal: Codex review: needs maintainer review before merge..

Author: resolve the current durable review findings, add focused regressions, and request a fresh exact-head review.

This is a triage classification, not a merge action. No branch changes were made.

Clean up fixture startup resources on cancellation and failure, and await owned listener exit before removing readiness state. Run proxy routing checks in isolated test hosts with a default-handler negative control.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@clawsweeper clawsweeper Bot added clawsweeper:intake:bug Preliminary submission classification; full source review pending. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action. and removed status: 👀 ready for maintainer look ClawSweeper has no concrete contributor-facing blocker left for this PR. clawsweeper:intake:bug Preliminary submission classification; full source review pending. labels Oct 20, 2026
Preserve the contributor's original commits and the maintainer P2 patch. Resolve fixture and MCP documentation conflicts while retaining main's interactive scenarios, device-token handling, and bounded connection cleanup.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@bkudiess
bkudiess merged commit c25f7ba into openclaw:main Oct 20, 2026
24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

P2 Normal priority bug or improvement with limited blast radius. proof: sufficient Contributor real behavior proof is sufficient. rating: 🐚 platinum hermit Good normal PR readiness with ordinary maintainer review expected. status: ⏳ waiting on author ClawSweeper has contributor-facing work open and is waiting for author action.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants