Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
OpenClaw's managed browser profile on native Linux cannot start or attach to its own Chrome instance when any form of SSRF strict mode is active in browser.ssrfPolicy. Chrome starts correctly and binds CDP on 127.0.0.1, /json/version returns a valid response including webSocketDebuggerUrl, and the port is reachable via curl and ss, but OpenClaw itself reports the profile as running: false / browser: unknown and refuses any browser operation.
Root cause is that the agent-facing ssrfPolicy is passed to the internal reachability probes in createProfileAvailability and applied to OpenClaw's own loopback profile.cdpUrl. The SSRF policy is intended to guard agent-initiated navigation, not the gateway's own lifecycle operations against its own managed browser.
Likely the same underlying bug as #64900 (WSL2 + Windows Edge remote CDP), but reproduced on a completely different platform with a local managed profile, which narrows it down to the gateway-side reachability logic rather than anything WSL/network-bridge specific.
Steps to reproduce
Environment:
- OpenClaw 2026.4.11 (769908e)
- Gentoo Linux (native, no containers, no WSL)
- Google Chrome 147.0.7727.55 (Gentoo binpkg — official .deb repackaged, not Snap/Flatpak)
- Node via npm global install
- Default
plugins.entries.browser.enabled: true
Config ~/.openclaw/openclaw.json:
{
"browser": {
"enabled": true,
"defaultProfile": "openclaw",
"ssrfPolicy": {
"hostnameAllowlist": ["*.example.com", "example.com"]
},
"profiles": {
"openclaw": {
"cdpPort": 18800,
"cdpUrl": "http://127.0.0.1:18800",
"color": "#FF4500"
}
}
}
}
(hostnameAllowlist is set as suggested by the "Strict-mode example" in the browser docs.)
Reproducer:
openclaw browser --browser-profile openclaw start
Expected behavior
OpenClaw launches Chrome on 127.0.0.1:18800, detects the CDP endpoint as reachable, profile status becomes running: true, subsequent open/snapshot/tabs calls work.
Actual behavior
After exactly ~8.4 seconds:
GatewayClientRequestError: Error: Chrome CDP websocket for profile "openclaw" is not reachable after start.
The ~8.4s delay matches the waitForCdpReadyAfterLaunch poll window in server-context.availability.ts exhausting its deadline. OpenClaw then kills the Chrome process it spawned.
Meanwhile, from an external shell while Chrome is alive (before OpenClaw kills it):
$ curl -s http://127.0.0.1:18800/json/version
{
"Browser": "Chrome/147.0.7727.55",
"Protocol-Version": "1.3",
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64) ...",
"V8-Version": "14.7.173.16",
"WebKit-Version": "537.36 (@7dc2b8f6f651b42c0a8f3634c9feb5e0b6b25c91)",
"webSocketDebuggerUrl": "ws://127.0.0.1:18800/devtools/browser/<uuid>"
}
$ ss -tlnp | grep 18800
LISTEN 0 10 127.0.0.1:18800 0.0.0.0:* users:(("chrome",pid=3680970,fd=92))
The CDP endpoint is objectively reachable — OpenClaw itself refuses to connect to its own child process.
Also reproduces symmetrically with attachOnly: true against a manually-launched Chrome (systemd user unit, same flags OpenClaw would use):
openclaw browser --browser-profile openclaw status
profile: openclaw
enabled: true
running: false # ← despite curl/ss showing Chrome alive on 127.0.0.1:18800
transport: cdp
cdpPort: 18800
cdpUrl: http://127.0.0.1:18800
browser: unknown # ← OpenClaw doesn't recognize its own-configured endpoint
detectedBrowser: chrome
detectedPath: /usr/bin/google-chrome-stable
openclaw browser --browser-profile openclaw open https://1.1.1.1
GatewayClientRequestError: Browser attachOnly is enabled and profile "openclaw" is not running.
In extensions/browser/src/browser/server-context.availability.ts (built file: server-context-*.js, around line 3440–3460 in 2026.4.11):
function createProfileAvailability({ opts, profile, state, getProfileState, setProfileRunning }) {
const capabilities = getBrowserProfileCapabilities(profile);
const resolveTimeouts = (timeoutMs) => resolveCdpReachabilityTimeouts({
profileIsLoopback: profile.cdpIsLoopback,
...
});
const isReachable = async (timeoutMs) => {
if (capabilities.usesChromeMcp) { ... }
const { httpTimeoutMs, wsTimeoutMs } = resolveTimeouts(timeoutMs);
return await isChromeCdpReady(profile.cdpUrl, httpTimeoutMs, wsTimeoutMs, state().resolved.ssrfPolicy);
};
const isHttpReachable = async (timeoutMs) => {
if (capabilities.usesChromeMcp) return await isReachable(timeoutMs);
const { httpTimeoutMs } = resolveTimeouts(timeoutMs);
return await isChromeReachable(profile.cdpUrl, httpTimeoutMs, state().resolved.ssrfPolicy);
};
Both isReachable and isHttpReachable pass state().resolved.ssrfPolicy as the third argument to the underlying probe functions (isChromeCdpReady, isChromeReachable, imported from chrome-*.js). These are internal probes against profile.cdpUrl — OpenClaw's own managed-browser endpoint.
When any strict-mode condition is active in the SSRF policy, 127.0.0.1 fails the probe. Two independent ways to hit this, both realistic:
- Explicit strict mode: user sets
hostnameAllowlist (which the docs actively recommend as the "Strict-mode example"). IPv4 literals don't match hostname globs, so the loopback probe is rejected.
- Implicit strict mode: default
dangerouslyAllowPrivateNetwork: false plus any configuration that triggers private-network blocking. 127.0.0.1 is per definition private/loopback.
ensureBrowserAvailable then fails in the isHttpReachable branch (~line 3519), and depending on profile mode either:
attachOnly: true → immediate throw at line ~3527: Browser attachOnly is enabled and profile "${name}" is not running.
- Managed launch →
launchOpenClawChrome → waitForCdpReadyAfterLaunch polls isReachable for CDP_READY_AFTER_LAUNCH_WINDOW_MS, exhausts the deadline (~8s), throws at line ~3490: Chrome CDP websocket for profile "${name}" is not reachable after start., then stopOpenClawChrome kills the spawned Chrome at line ~3533.
Relevant observation: profile.cdpIsLoopback is already computed and used in an adjacent branch at ~line 3524 for a different control-flow decision. The flag is present — it's just not propagated into the SSRF exemption path, because there is no SSRF exemption path for internal gateway↔managed-browser traffic at all.
The agent-facing SSRF policy exists to protect against an agent being tricked into navigating to an internal/private URL (DNS rebinding, SSRF-style pivot attacks, etc.). It has nothing to do with whether the gateway is allowed to talk to its own explicitly-configured, explicitly-bound managed browser process over loopback. Applying it to internal probes conflates two different threat models:
- Agent-initiated navigation (untrusted input, needs SSRF guarding) →
openclaw browser open <url>
- Gateway-internal lifecycle operations (trusted input, configured by the operator) →
isHttpReachable(profile.cdpUrl)
The second category should never be gated by the first category's policy. The operator already declared intent by configuring browser.profiles.<name>.cdpUrl (or accepting the default loopback). Re-validating that against a security policy that's scoped to untrusted agent input means the operator can't express a meaningful SSRF policy for agent traffic without also breaking the managed browser.
Adding explicit loopback allowances to the SSRF policy makes the managed browser usable again:
{
"browser": {
"ssrfPolicy": {
"dangerouslyAllowPrivateNetwork": true,
"hostnameAllowlist": ["*"],
"allowedHostnames": ["127.0.0.1", "localhost"]
}
}
}
This works but is misleading - it forces the operator to "dangerously" allow private-network access just to re-enable the gateway's own internal loopback communication, and it weakens the SSRF policy for agent navigation as a side effect of fixing an internal plumbing issue.
OpenClaw version
2026.4.11 (769908e)
Operating system
Gentoo Linux (native, no container, no WSL, no snap/flatpak or other bullshit)
Install method
npm global baby
Model
Not relevant - failure occurs before any agent traffic.
Provider / routing chain
Not relevant - failure is gateway-internal against its own managed browser process on 127.0.0.1.
Additional provider/model setup details
Internal probes issued from createProfileAvailability against profile.cdpUrl should not flow through state().resolved.ssrfPolicy. Instead:
- If
profile.cdpIsLoopback === true (the flag is already there), skip SSRF checks for isChromeReachable / isChromeCdpReady entirely — loopback to the gateway's own managed-browser process is trust-by-configuration, not by policy.
- For remote CDP profiles (explicitly configured
cdpUrl on a non-loopback host), either:
- Still skip the agent SSRF policy, since the operator has explicitly declared this endpoint as trusted by adding it to the profile config, or
- Apply a separate, infrastructure-scoped SSRF policy (
browser.internal.ssrfPolicy or similar) that defaults to permissive for explicitly-configured endpoints.
Alternative minimal fix: keep the SSRF policy application, but always implicitly include the configured profile.cdpUrl host in the effective allowlist at resolution time, so the operator's declared CDP endpoint is self-exempting regardless of broader policy.
Logs, screenshots, and evidence
Gateway log excerpt (managed launch path):
19:54:31+00:00 info browser/chrome {"subsystem":"browser/chrome"} 🦞 openclaw browser started (chrome) profile "openclaw" on 127.0.0.1:18800 (pid 3649976)
19:54:39+00:00 info gateway/ws {"subsystem":"gateway/ws"} ⇄ res ✗ browser.request 8460ms errorCode=UNAVAILABLE errorMessage=Error: Chrome CDP websocket for profile "openclaw" is not reachable after start. conn=20f922d0…c17b id=0fd46ed7…701b
19:54:39+00:00 error GatewayClientRequestError: Error: Chrome CDP websocket for profile "openclaw" is not reachable after start
Chrome is alive, bound, responsive — OpenClaw refuses to talk to it:
$ curl -s http://127.0.0.1:18800/json/version | jq .Browser
"Chrome/147.0.7727.55"
$ ss -tlnp | grep 18800
LISTEN 0 10 127.0.0.1:18800 0.0.0.0:* users:(("chrome",pid=3680970,fd=92))
$ openclaw browser --browser-profile openclaw status
running: false
browser: unknown
cdpUrl: http://127.0.0.1:18800
Stable `~8.4s` delay on every failed managed-launch attempt matches `waitForCdpReadyAfterLaunch` exhausting its poll deadline.
Impact and severity
This is extremely annoying to unsuspecting new users.
High for operators who want to run strict SSRF policies.
The docs explicitly recommend hostnameAllowlist for strict deployments ("Strict-mode example" section in the browser docs), so any operator following that recommendation immediately breaks their managed browser. The bug is not a misuse of the configuration — it's a logical conflict between two documented features that can't currently coexist.
Additional information
Related: #64900 (WSL2 + Windows Edge remote CDP, same running: false / browser: unknown / attachOnly is enabled and profile ... is not running symptom pattern from a different platform). That report is tagged as a regression. The fact that the same failure mode reproduces on native Linux loopback with a local managed profile suggests the root cause is in the gateway-side reachability logic in createProfileAvailability, independent of network-bridge or remote-host specifics.
Bug type
Regression (worked before, now fails)
Beta release blocker
No
Summary
OpenClaw's managed browser profile on native Linux cannot start or attach to its own Chrome instance when any form of SSRF strict mode is active in
browser.ssrfPolicy. Chrome starts correctly and binds CDP on127.0.0.1,/json/versionreturns a valid response includingwebSocketDebuggerUrl, and the port is reachable viacurlandss, but OpenClaw itself reports the profile asrunning: false/browser: unknownand refuses any browser operation.Root cause is that the agent-facing
ssrfPolicyis passed to the internal reachability probes increateProfileAvailabilityand applied to OpenClaw's own loopbackprofile.cdpUrl. The SSRF policy is intended to guard agent-initiated navigation, not the gateway's own lifecycle operations against its own managed browser.Likely the same underlying bug as #64900 (WSL2 + Windows Edge remote CDP), but reproduced on a completely different platform with a local managed profile, which narrows it down to the gateway-side reachability logic rather than anything WSL/network-bridge specific.
Steps to reproduce
Environment:
plugins.entries.browser.enabled: trueConfig
~/.openclaw/openclaw.json:{ "browser": { "enabled": true, "defaultProfile": "openclaw", "ssrfPolicy": { "hostnameAllowlist": ["*.example.com", "example.com"] }, "profiles": { "openclaw": { "cdpPort": 18800, "cdpUrl": "http://127.0.0.1:18800", "color": "#FF4500" } } } }(
hostnameAllowlistis set as suggested by the "Strict-mode example" in the browser docs.)Reproducer:
Expected behavior
OpenClaw launches Chrome on
127.0.0.1:18800, detects the CDP endpoint as reachable, profile status becomesrunning: true, subsequentopen/snapshot/tabscalls work.Actual behavior
After exactly ~8.4 seconds:
The
~8.4sdelay matches thewaitForCdpReadyAfterLaunchpoll window inserver-context.availability.tsexhausting its deadline. OpenClaw then kills the Chrome process it spawned.Meanwhile, from an external shell while Chrome is alive (before OpenClaw kills it):
$ curl -s http://127.0.0.1:18800/json/version { "Browser": "Chrome/147.0.7727.55", "Protocol-Version": "1.3", "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) ...", "V8-Version": "14.7.173.16", "WebKit-Version": "537.36 (@7dc2b8f6f651b42c0a8f3634c9feb5e0b6b25c91)", "webSocketDebuggerUrl": "ws://127.0.0.1:18800/devtools/browser/<uuid>" } $ ss -tlnp | grep 18800 LISTEN 0 10 127.0.0.1:18800 0.0.0.0:* users:(("chrome",pid=3680970,fd=92))The CDP endpoint is objectively reachable — OpenClaw itself refuses to connect to its own child process.
Also reproduces symmetrically with
attachOnly: trueagainst a manually-launched Chrome (systemd user unit, same flags OpenClaw would use):In
extensions/browser/src/browser/server-context.availability.ts(built file:server-context-*.js, around line 3440–3460 in 2026.4.11):Both
isReachableandisHttpReachablepassstate().resolved.ssrfPolicyas the third argument to the underlying probe functions (isChromeCdpReady,isChromeReachable, imported fromchrome-*.js). These are internal probes againstprofile.cdpUrl— OpenClaw's own managed-browser endpoint.When any strict-mode condition is active in the SSRF policy,
127.0.0.1fails the probe. Two independent ways to hit this, both realistic:hostnameAllowlist(which the docs actively recommend as the "Strict-mode example"). IPv4 literals don't match hostname globs, so the loopback probe is rejected.dangerouslyAllowPrivateNetwork: falseplus any configuration that triggers private-network blocking.127.0.0.1is per definition private/loopback.ensureBrowserAvailablethen fails in theisHttpReachablebranch (~line 3519), and depending on profile mode either:attachOnly: true→ immediate throw at line ~3527:Browser attachOnly is enabled and profile "${name}" is not running.launchOpenClawChrome→waitForCdpReadyAfterLaunchpollsisReachableforCDP_READY_AFTER_LAUNCH_WINDOW_MS, exhausts the deadline (~8s), throws at line ~3490:Chrome CDP websocket for profile "${name}" is not reachable after start., thenstopOpenClawChromekills the spawned Chrome at line ~3533.Relevant observation:
profile.cdpIsLoopbackis already computed and used in an adjacent branch at ~line 3524 for a different control-flow decision. The flag is present — it's just not propagated into the SSRF exemption path, because there is no SSRF exemption path for internal gateway↔managed-browser traffic at all.The agent-facing SSRF policy exists to protect against an agent being tricked into navigating to an internal/private URL (DNS rebinding, SSRF-style pivot attacks, etc.). It has nothing to do with whether the gateway is allowed to talk to its own explicitly-configured, explicitly-bound managed browser process over loopback. Applying it to internal probes conflates two different threat models:
openclaw browser open <url>isHttpReachable(profile.cdpUrl)The second category should never be gated by the first category's policy. The operator already declared intent by configuring
browser.profiles.<name>.cdpUrl(or accepting the default loopback). Re-validating that against a security policy that's scoped to untrusted agent input means the operator can't express a meaningful SSRF policy for agent traffic without also breaking the managed browser.Adding explicit loopback allowances to the SSRF policy makes the managed browser usable again:
{ "browser": { "ssrfPolicy": { "dangerouslyAllowPrivateNetwork": true, "hostnameAllowlist": ["*"], "allowedHostnames": ["127.0.0.1", "localhost"] } } }This works but is misleading - it forces the operator to "dangerously" allow private-network access just to re-enable the gateway's own internal loopback communication, and it weakens the SSRF policy for agent navigation as a side effect of fixing an internal plumbing issue.
OpenClaw version
2026.4.11 (769908e)
Operating system
Gentoo Linux (native, no container, no WSL, no snap/flatpak or other bullshit)
Install method
npm global baby
Model
Not relevant - failure occurs before any agent traffic.
Provider / routing chain
Not relevant - failure is gateway-internal against its own managed browser process on
127.0.0.1.Additional provider/model setup details
Internal probes issued from
createProfileAvailabilityagainstprofile.cdpUrlshould not flow throughstate().resolved.ssrfPolicy. Instead:profile.cdpIsLoopback === true(the flag is already there), skip SSRF checks forisChromeReachable/isChromeCdpReadyentirely — loopback to the gateway's own managed-browser process is trust-by-configuration, not by policy.cdpUrlon a non-loopback host), either:browser.internal.ssrfPolicyor similar) that defaults to permissive for explicitly-configured endpoints.Alternative minimal fix: keep the SSRF policy application, but always implicitly include the configured
profile.cdpUrlhost in the effective allowlist at resolution time, so the operator's declared CDP endpoint is self-exempting regardless of broader policy.Logs, screenshots, and evidence
Gateway log excerpt (managed launch path): 19:54:31+00:00 info browser/chrome {"subsystem":"browser/chrome"} 🦞 openclaw browser started (chrome) profile "openclaw" on 127.0.0.1:18800 (pid 3649976) 19:54:39+00:00 info gateway/ws {"subsystem":"gateway/ws"} ⇄ res ✗ browser.request 8460ms errorCode=UNAVAILABLE errorMessage=Error: Chrome CDP websocket for profile "openclaw" is not reachable after start. conn=20f922d0…c17b id=0fd46ed7…701b 19:54:39+00:00 error GatewayClientRequestError: Error: Chrome CDP websocket for profile "openclaw" is not reachable after start Chrome is alive, bound, responsive — OpenClaw refuses to talk to it: $ curl -s http://127.0.0.1:18800/json/version | jq .Browser "Chrome/147.0.7727.55" $ ss -tlnp | grep 18800 LISTEN 0 10 127.0.0.1:18800 0.0.0.0:* users:(("chrome",pid=3680970,fd=92)) $ openclaw browser --browser-profile openclaw status running: false browser: unknown cdpUrl: http://127.0.0.1:18800 Stable `~8.4s` delay on every failed managed-launch attempt matches `waitForCdpReadyAfterLaunch` exhausting its poll deadline.Impact and severity
This is extremely annoying to unsuspecting new users.
High for operators who want to run strict SSRF policies.
The docs explicitly recommend
hostnameAllowlistfor strict deployments ("Strict-mode example" section in the browser docs), so any operator following that recommendation immediately breaks their managed browser. The bug is not a misuse of the configuration — it's a logical conflict between two documented features that can't currently coexist.Additional information
Related: #64900 (WSL2 + Windows Edge remote CDP, same
running: false/browser: unknown/attachOnly is enabled and profile ... is not runningsymptom pattern from a different platform). That report is tagged as a regression. The fact that the same failure mode reproduces on native Linux loopback with a local managed profile suggests the root cause is in the gateway-side reachability logic increateProfileAvailability, independent of network-bridge or remote-host specifics.