Repository navigation
fix(browser): keep browser.proxy off the process proxy - #1628
Conversation
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>
|
🦞👀 Pull request received. I will update this pull request when review starts. ClawSweeper review completeClawSweeper finished reviewing this revision. The review result is being finalized. |
|
Codex review: needs changes before merge. What this changesThis 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.
Review scores
ProductKind: Bug fix · Worth it: Yes · Fix scope: Complete 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 Before merge
FindingsNone. Tests
Agent review detailsHow this fits togetherThe 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]
Technical reviewBest 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
TestingProof path: shipped entry point. SecurityNone. EvidenceWhat I checked:
Likely related people:
LabelsLabel changes:
Label justifications:
Rating scale6/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. WorkflowClawSweeper edits this one comment on every review. Comment HistoryReview history (16 earlier review cycles; latest 8 shown)
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>
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>
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>
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>
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>
|
Global triage: HOLD_FOR_AUTHOR. Take confidence 20%; recommendation confidence 98%; effort medium; risk high. Reviewed exact head 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>
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>
What Problem This Solves
browser.proxytalks to127.0.0.1, but its HTTP client followed the process proxy. WithHTTP_PROXYorALL_PROXYset, 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 unlessNO_PROXYsays 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_PROXYandALL_PROXYpointed at a local listener on79259c3c. 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.GatewayFixtureHost --browser-proxystarted 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.A gateway
nodes invokethrough the installed WSL gateway was not run.On head
0b29606d23a41615caa2bf0ac8c19eaeffcfdb64, a live child withOPENCLAW_GATEWAY_FIXTURE=1calledFixtureLoopbackListenerOwner.IsOwnedByCurrentProcessParent, thenBrowserProxyCapability.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.proxyreturned 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 on127.0.0.1:63775. The child could no longer open the old parent id. The owner returned false,browser.proxyreturned the same block, and the replacement listener accepted 0 connections.Change Type
Scope
winnodeRequired 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
af5ad2d363fae7fff4ed58b476ba21dcf4714035in worktreeC:\Users\sebta\wt-wn-1628. This head mergesorigin/main94006b3672a034983e80a78dafe311e24ba80cce, 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. Onbd8b2d0b32149fd46f51095e09329d8e4ee80e17, CI failedPublishedVerifierEnforcesRecoveryAndModelPhaseTokens(blockModel: True)withObjectDisposedExceptionfromSemaphoreSlim.ReleaseinFixtureGatewayServer.CloseConnectionsAsync. On03e039d8a6c39c25c7fc048e2bc7059e40ca1d03, the UI job failedStopDuringStartup_WaitsForInitializationThenCleansUp:Assert.False(stopTask.IsCompleted)saw True because the fake start block timed out at 2 seconds.OPENCLAW_REPO_ROOTwas that worktree../build.ps1: exit 0dotnet test ./tests/OpenClaw.Shared.Tests/OpenClaw.Shared.Tests.csproj --no-restore: 4267 passed, 33 skipped, 0 failed, total 4300dotnet test ./tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj --no-restore: 3996 passed, 0 failed, total 3996dotnet test ./tests/OpenClaw.WinNode.Cli.Tests/OpenClaw.WinNode.Cli.Tests.csproj --no-restore: 129 passed, 0 failed, total 129PublishedVerifierEnforcesRecoveryAndModelPhaseTokens: 2 passedCloseConnectionsAsync_ToleratesAConnectionHandlerThatDisposedItsSendLock: passedAudioPipelineFirstAudioTimeoutTestsonaf5ad2d363fae7fff4ed58b476ba21dcf4714035: 13 passed, 0 failed. The product build, Shared, and Tray counts above are from03e039d8a6c39c25c7fc048e2bc7059e40ca1d03. 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.proxyproduction code.GatewayFixtureIsolationnow requires the fixture local data directory to match the legacy app-data root plusOpenClawTray.On the earlier head
f9c993c35202b35ed90dde4aaa0189199006395f: ConnectionFixtureLoopbackListenerOwnerTests6 passed,RealNodeClient_FixtureAcceptsTheNodeRole1 passed, andBrowserProxyProfileKeepsExecutionCapabilitiesOff1 passed.RealNodeClient_FixtureAcceptsTheNodeRoleis part of the Shared suite that passed on this head.Real behavior proof
browser.proxycould send the gateway bearer token to the process HTTP proxy.C:\Users\sebta\wt-wn-f009, headf9c993c35202b35ed90dde4aaa0189199006395f.HTTP_PROXYandALL_PROXYpointed at aTcpListeneron127.0.0.1on79259c3c. On this head,GatewayFixtureHost --browser-proxylaunched the built tray.winnode --list-toolsincludedbrowser.proxy.winnode --command browser.proxy --params {"path":"/tabs"}called the fixture control host.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_DoesNotSendTheRequestdenies 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 invokethrough the installed WSL gateway was not run. The tray browser page was not opened. The direct-proxy listener proof was collected on79259c3c. 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 on0b29606d23a41615caa2bf0ac8c19eaeffcfdb64.Screenshot or artifact links verified?
N/ASecurity Impact
NoYesNoNoNoYes, explain the risk and mitigation: The gateway bearer token is still sent to the loopback browser-control port. It is no longer sent throughHTTP_PROXYorALL_PROXY.Compatibility and Migration
YesNoNoReview Conversations
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.