mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:18:37 +00:00
fix: make browser relay bind address configurable (#39364) (thanks @mvanhorn)
This commit is contained in:
@@ -30,6 +30,7 @@ Docs: https://docs.openclaw.ai
|
|||||||
- Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for `/json/*` tab operations so local `ws://` / `wss://` profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.
|
- Browser/CDP: normalize loopback direct WebSocket CDP URLs back to HTTP(S) for `/json/*` tab operations so local `ws://` / `wss://` profiles can still list, focus, open, and close tabs after the new direct-WS support lands. (#31085) Thanks @shrey150.
|
||||||
- Browser/CDP: rewrite wildcard `ws://0.0.0.0` and `ws://[::]` debugger URLs from remote `/json/version` responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.
|
- Browser/CDP: rewrite wildcard `ws://0.0.0.0` and `ws://[::]` debugger URLs from remote `/json/version` responses back to the external CDP host/port, fixing Browserless-style container endpoints. (#17760) Thanks @joeharouni.
|
||||||
- Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with `tab not found`, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.
|
- Browser/extension relay: wait briefly for a previously attached Chrome tab to reappear after transient relay drops before failing with `tab not found`, reducing noisy reconnect flakes. (#32461) Thanks @AaronWander.
|
||||||
|
- Browser/extension relay: add `browser.relayBindHost` so the Chrome relay can bind to an explicit non-loopback address for WSL2 and other cross-namespace setups, while preserving loopback-only defaults. (#39364) Thanks @mvanhorn.
|
||||||
- Context engine registry/bundled builds: share the registry state through a `globalThis` singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.
|
- Context engine registry/bundled builds: share the registry state through a `globalThis` singleton so duplicated bundled module copies can resolve engines registered by each other at runtime, with regression coverage for duplicate-module imports. (#40115) thanks @jalehman.
|
||||||
|
|
||||||
## 2026.3.7
|
## 2026.3.7
|
||||||
|
|||||||
@@ -2354,6 +2354,7 @@ See [Plugins](/tools/plugin).
|
|||||||
// headless: false,
|
// headless: false,
|
||||||
// noSandbox: false,
|
// noSandbox: false,
|
||||||
// extraArgs: [],
|
// extraArgs: [],
|
||||||
|
// relayBindHost: "0.0.0.0", // only when the extension relay must be reachable across namespaces (for example WSL2)
|
||||||
// executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
// executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
|
||||||
// attachOnly: false,
|
// attachOnly: false,
|
||||||
},
|
},
|
||||||
@@ -2370,6 +2371,7 @@ See [Plugins](/tools/plugin).
|
|||||||
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
|
- Control service: loopback only (port derived from `gateway.port`, default `18791`).
|
||||||
- `extraArgs` appends extra launch flags to local Chromium startup (for example
|
- `extraArgs` appends extra launch flags to local Chromium startup (for example
|
||||||
`--disable-gpu`, window sizing, or debug flags).
|
`--disable-gpu`, window sizing, or debug flags).
|
||||||
|
- `relayBindHost` changes where the Chrome extension relay listens. Leave unset for loopback-only access; set an explicit non-loopback bind address such as `0.0.0.0` only when the relay must cross a namespace boundary (for example WSL2) and the host network is already trusted.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -328,6 +328,19 @@ Notes:
|
|||||||
|
|
||||||
- This mode relies on Playwright-on-CDP for most operations (screenshots/snapshots/actions).
|
- This mode relies on Playwright-on-CDP for most operations (screenshots/snapshots/actions).
|
||||||
- Detach by clicking the extension icon again.
|
- Detach by clicking the extension icon again.
|
||||||
|
- Leave the relay loopback-only by default. If the relay must be reachable from a different network namespace (for example Gateway in WSL2, Chrome on Windows), set `browser.relayBindHost` to an explicit bind address such as `0.0.0.0` while keeping the surrounding network private and authenticated.
|
||||||
|
|
||||||
|
WSL2 / cross-namespace example:
|
||||||
|
|
||||||
|
```json5
|
||||||
|
{
|
||||||
|
browser: {
|
||||||
|
enabled: true,
|
||||||
|
relayBindHost: "0.0.0.0",
|
||||||
|
defaultProfile: "chrome",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Isolation guarantees
|
## Isolation guarantees
|
||||||
|
|
||||||
|
|||||||
@@ -161,6 +161,7 @@ Debugging: `openclaw sandbox explain`
|
|||||||
|
|
||||||
- Keep the Gateway and node host on the same tailnet; avoid exposing relay ports to LAN or public Internet.
|
- Keep the Gateway and node host on the same tailnet; avoid exposing relay ports to LAN or public Internet.
|
||||||
- Pair nodes intentionally; disable browser proxy routing if you don’t want remote control (`gateway.nodes.browser.mode="off"`).
|
- Pair nodes intentionally; disable browser proxy routing if you don’t want remote control (`gateway.nodes.browser.mode="off"`).
|
||||||
|
- Leave the relay on loopback unless you have a real cross-namespace need. For WSL2 or similar split-host setups, set `browser.relayBindHost` to an explicit bind address such as `0.0.0.0`, then keep access constrained with Gateway auth, node pairing, and a private network.
|
||||||
|
|
||||||
## How “extension path” works
|
## How “extension path” works
|
||||||
|
|
||||||
|
|||||||
@@ -1202,4 +1202,23 @@ describe("chrome extension relay server", () => {
|
|||||||
},
|
},
|
||||||
RELAY_TEST_TIMEOUT_MS,
|
RELAY_TEST_TIMEOUT_MS,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it(
|
||||||
|
"restarts the relay when bindHost changes for the same port",
|
||||||
|
async () => {
|
||||||
|
const port = await getFreePort();
|
||||||
|
cdpUrl = `http://127.0.0.1:${port}`;
|
||||||
|
|
||||||
|
const initial = await ensureChromeExtensionRelayServer({ cdpUrl });
|
||||||
|
expect(initial.bindHost).toBe("127.0.0.1");
|
||||||
|
|
||||||
|
const rebound = await ensureChromeExtensionRelayServer({
|
||||||
|
cdpUrl,
|
||||||
|
bindHost: "0.0.0.0",
|
||||||
|
});
|
||||||
|
expect(rebound.bindHost).toBe("0.0.0.0");
|
||||||
|
expect(rebound.port).toBe(port);
|
||||||
|
},
|
||||||
|
RELAY_TEST_TIMEOUT_MS,
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -234,12 +234,20 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
|
|
||||||
const existing = relayRuntimeByPort.get(info.port);
|
const existing = relayRuntimeByPort.get(info.port);
|
||||||
if (existing) {
|
if (existing) {
|
||||||
return existing.server;
|
if (existing.server.bindHost !== bindHost) {
|
||||||
|
await existing.server.stop();
|
||||||
|
} else {
|
||||||
|
return existing.server;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const inFlight = relayInitByPort.get(info.port);
|
const inFlight = relayInitByPort.get(info.port);
|
||||||
if (inFlight) {
|
if (inFlight) {
|
||||||
return await inFlight;
|
const server = await inFlight;
|
||||||
|
if (server.bindHost === bindHost) {
|
||||||
|
return server;
|
||||||
|
}
|
||||||
|
await server.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
const extensionReconnectGraceMs = envMsOrDefault(
|
const extensionReconnectGraceMs = envMsOrDefault(
|
||||||
@@ -998,12 +1006,13 @@ export async function ensureChromeExtensionRelayServer(opts: {
|
|||||||
|
|
||||||
const addr = server.address() as AddressInfo | null;
|
const addr = server.address() as AddressInfo | null;
|
||||||
const port = addr?.port ?? info.port;
|
const port = addr?.port ?? info.port;
|
||||||
|
const actualBindHost = addr?.address || bindHost;
|
||||||
const host = info.host;
|
const host = info.host;
|
||||||
const baseUrl = `${new URL(info.baseUrl).protocol}//${host}:${port}`;
|
const baseUrl = `${new URL(info.baseUrl).protocol}//${host}:${port}`;
|
||||||
|
|
||||||
const relay: ChromeExtensionRelayServer = {
|
const relay: ChromeExtensionRelayServer = {
|
||||||
host,
|
host,
|
||||||
bindHost,
|
bindHost: actualBindHost,
|
||||||
port,
|
port,
|
||||||
baseUrl,
|
baseUrl,
|
||||||
cdpWsUrl: `ws://${host}:${port}/cdp`,
|
cdpWsUrl: `ws://${host}:${port}/cdp`,
|
||||||
|
|||||||
@@ -250,6 +250,8 @@ export const FIELD_HELP: Record<string, string> = {
|
|||||||
"Starting local CDP port used for auto-allocated browser profile ports. Increase this when host-level port defaults conflict with other local services.",
|
"Starting local CDP port used for auto-allocated browser profile ports. Increase this when host-level port defaults conflict with other local services.",
|
||||||
"browser.defaultProfile":
|
"browser.defaultProfile":
|
||||||
"Default browser profile name selected when callers do not explicitly choose a profile. Use a stable low-privilege profile as the default to reduce accidental cross-context state use.",
|
"Default browser profile name selected when callers do not explicitly choose a profile. Use a stable low-privilege profile as the default to reduce accidental cross-context state use.",
|
||||||
|
"browser.relayBindHost":
|
||||||
|
"Bind IP address for the Chrome extension relay listener. Leave unset for loopback-only access, or set an explicit non-loopback IP such as 0.0.0.0 only when the relay must be reachable across network namespaces (for example WSL2) and the surrounding network is already trusted.",
|
||||||
"browser.profiles":
|
"browser.profiles":
|
||||||
"Named browser profile connection map used for explicit routing to CDP ports or URLs with optional metadata. Keep profile names consistent and avoid overlapping endpoint definitions.",
|
"Named browser profile connection map used for explicit routing to CDP ports or URLs with optional metadata. Keep profile names consistent and avoid overlapping endpoint definitions.",
|
||||||
"browser.profiles.*.cdpPort":
|
"browser.profiles.*.cdpPort":
|
||||||
|
|||||||
@@ -118,6 +118,7 @@ export const FIELD_LABELS: Record<string, string> = {
|
|||||||
"browser.attachOnly": "Browser Attach-only Mode",
|
"browser.attachOnly": "Browser Attach-only Mode",
|
||||||
"browser.cdpPortRangeStart": "Browser CDP Port Range Start",
|
"browser.cdpPortRangeStart": "Browser CDP Port Range Start",
|
||||||
"browser.defaultProfile": "Browser Default Profile",
|
"browser.defaultProfile": "Browser Default Profile",
|
||||||
|
"browser.relayBindHost": "Browser Relay Bind Address",
|
||||||
"browser.profiles": "Browser Profiles",
|
"browser.profiles": "Browser Profiles",
|
||||||
"browser.profiles.*.cdpPort": "Browser Profile CDP Port",
|
"browser.profiles.*.cdpPort": "Browser Profile CDP Port",
|
||||||
"browser.profiles.*.cdpUrl": "Browser Profile CDP URL",
|
"browser.profiles.*.cdpUrl": "Browser Profile CDP URL",
|
||||||
|
|||||||
Reference in New Issue
Block a user