How Mako's Computer-Use Module Interacts with Browsers Using Chrome DevTools Protocol (CDP)
Mako's computer-use module exposes a local, secured WebSocket endpoint that tunnels Chrome DevTools Protocol commands to an Electron browser window, enabling AI-driven automation without embedding a separate Chrome instance.
Mako is an open-source AI agent framework that provides computer-use capabilities—allowing models to see and interact with graphical interfaces. Rather than launching a standalone Chrome browser, Mako reuses its existing Electron-rendered window as the automation target. The computer-use module achieves this by wrapping the window's native CDP interface in a hardened local bridge that the model can safely invoke.
How the CDP Bridge Architecture Works
The interaction follows a three-layer design: the bridge itself creates the secured endpoint, the desktop controller manages its lifecycle per window, and the runtime tools translate model intentions into CDP commands.
CdpBridge: The Secured Local Endpoint
At the core is CdpBridge, implemented in apps/desktop/src/main/browser/cdp-bridge.ts. This class creates an HTTP server that listens exclusively on 127.0.0.1 with a cryptographically random secret path, then upgrades connections to a WebSocket that tunnels directly to the Electron debugger.
// Simplified bridge initialization from cdp-bridge.ts
const server = http.createServer();
server.listen(0, '127.0.0.1', () => {
const port = server.address().port;
const secret = crypto.randomBytes(32).toString('hex');
this.endpoint = `ws://127.0.0.1:${port}/${secret}`;
});
The bridge enforces multiple security constraints baked into the source:
- Loopback-only binding — The server refuses non-local connections via explicit
127.0.0.1binding. - Constant-time secret validation — The path secret is compared using
crypto.timingSafeEqualto prevent timing attacks. - Single-connection limit — Only one concurrent WebSocket client is permitted; subsequent attempts receive
403 Forbidden. - Host-header pinning — Requests with mismatched
Hostheaders are rejected immediately.
These measures ensure that even if the endpoint leaks, remote attackers cannot exploit it.
Attaching the Electron Debugger
Once the WebSocket server accepts a client, CdpBridge attaches Electron's built-in debugger module to the target WebContents:
// From cdp-bridge.ts: debugger attachment and command forwarding
this.debugger = webContents.debugger;
await this.debugger.attach('1.3'); // Chrome DevTools Protocol 1.3
this.debugger.on('message', (event, method, params) => {
// Forward CDP events from browser to WebSocket client
if (this.ws) this.ws.send(JSON.stringify({ method, params }));
});
this.ws.on('message', (data) => {
// Forward CDP commands from client to browser
const { id, method, params } = JSON.parse(data);
this.debugger.sendCommand(method, params)
.then((result) => this.ws.send(JSON.stringify({ id, result })))
.catch((error) => this.ws.send(JSON.stringify({ id, error })));
});
This bidirectional forwarding allows the computer-use model to issue any standard CDP command—Input.dispatchMouseEvent, Runtime.evaluate, DOM.performSearch, Page.captureScreenshot—exactly as it would to a standalone Chrome instance.
Desktop Controller: Managing Bridge Lifecycle
The BrowserController in apps/desktop/src/main/browser/controller.ts owns the CdpBridge lifecycle, ensuring one bridge per WebContents with lazy initialization:
// From controller.ts: lazy bridge creation
async getAutomationEndpoint(): Promise<AutomationEndpoint> {
if (!this.automation) {
this.automation = new CdpBridge(this.webContents);
}
return this.automation.start();
}
The controller also handles cleanup. If the WebContents is destroyed, navigated, or another debugger attaches (e.g., the user opens Chrome DevTools), the bridge detects this and terminates gracefully. It propagates typed errors—CdpBridgeError with codes target-busy, target-destroyed, or bridge-start-timeout—allowing upstream code to distinguish retryable from fatal failures.
Runtime Integration: From Model Intent to CDP Action
The computer-use runtime in packages/runtime/src/computer-use-tools.ts consumes the AutomationEndpoint returned by the controller. It establishes a WebSocket connection to cdpEndpoint and wraps CDP operations in higher-level abstractions.
Example: Mouse Click Automation
// From computer-use-tools.ts pattern: executing a click via CDP
const client = await connectCdp(cdpEndpoint);
// Press
await client.send('Input.dispatchMouseEvent', {
type: 'mousePressed',
x: 150,
y: 200,
button: 'left',
clickCount: 1,
modifiers: 0
});
// Release
await client.send('Input.dispatchMouseEvent', {
type: 'mouseReleased',
x: 150,
y: 200,
button: 'left',
clickCount: 1
});
Example: DOM Query and Interaction
// Search for an element and retrieve its box model
const searchResult = await client.send('DOM.performSearch', {
query: 'button[aria-label="Submit"]'
});
await client.send('DOM.getSearchResults', {
searchId: searchResult.searchId,
fromIndex: 0,
toIndex: 1
});
// Scroll into view if needed
await client.send('DOM.scrollIntoViewIfNeeded', {
nodeId: nodeId
});
The model never manages WebSocket details directly; the runtime handles reconnection, timeout detection, and error classification using the CdpBridgeError taxonomy defined in the bridge.
Error Handling and Security Boundaries
Mako's CDP integration is designed for hostile multi-tenant scenarios where model-generated code runs unsandboxed. The bridge's error hierarchy enables precise recovery strategies:
| Error Code | Trigger | Model Response |
|---|---|---|
target-busy |
External debugger attached (e.g., DevTools) | Delay and retry, or notify user to close DevTools |
target-destroyed |
Window closed or navigated | Reacquire WebContents from controller |
bridge-start-timeout |
Debugger attachment stalled | Abort current operation, log diagnostic |
// Error handling pattern from computer-use-tools.ts
try {
const { cdpEndpoint } = await automation.start();
// ... execute CDP commands
} catch (err) {
if (err instanceof CdpBridgeError) {
switch (err.code) {
case 'target-busy':
await sleep(500);
return retry(); // Exponential backoff
case 'target-destroyed':
throw new SessionTerminatedError('Browser window closed');
case 'bridge-start-timeout':
metrics.record('cdp_bridge_timeout');
throw new InfrastructureError('Debugger unresponsive');
}
}
throw err; // Unknown error, propagate
}
Configuration and Protocol Specification
The CDP bridge behavior is configurable through Mako's core computer-use configuration in packages/core/src/computer-use.ts. Key parameters include:
cdpPort— Fixed port override (default:0for ephemeral assignment)bridgeTimeoutMs— Maximum time to wait for debugger attachmentsecretLength— Bytes of entropy for the path secret (default: 32)
For protocol documentation between the backend bridge and client tools, see packages/computer-use/src/maka-cu-protocol.ts, which defines message schemas and version compatibility guarantees.
Key Source Files
apps/desktop/src/main/browser/cdp-bridge.ts—CdpBridgeclass: WebSocket server, security enforcement, debugger forwarding.apps/desktop/src/main/browser/controller.ts—BrowserController: per-window bridge lifecycle management.packages/runtime/src/computer-use-tools.ts— Runtime CDP client operations and error handling.packages/core/src/computer-use.ts— Configuration schema for CDP bridge parameters.packages/computer-use/src/maka-cu-protocol.ts— Protocol specification for bridge communication.apps/desktop/src/main/__tests__/cdp-bridge.test.ts— Security contract and error scenario validation.
Summary
- Mako's computer-use module exposes browser automation through a local CDP bridge rather than external Chrome processes.
CdpBridge(cdp-bridge.ts) creates a loopback-only WebSocket endpoint with cryptographic path secrets, attaching Electron's debugger to forward CDP traffic.- The desktop controller (
controller.ts) lazily instantiates bridges perWebContentsand routesAutomationEndpointobjects to the runtime. - Runtime tools (
computer-use-tools.ts) translate model actions into CDP commands likeInput.dispatchMouseEventandDOM.performSearch, handling reconnection and typed errors. - Security boundaries include single-connection limits, constant-time secret validation, and automatic teardown on external debugger interference.
Frequently Asked Questions
Does Mako spawn a separate Chrome process for computer-use automation?
No. Mako reuses the existing Electron WebContents that renders the browser UI. The CdpBridge attaches to this window's built-in debugger, avoiding the overhead and permission complexity of launching an external Chrome binary. This design is implemented in apps/desktop/src/main/browser/cdp-bridge.ts.
What prevents malicious code from accessing the CDP endpoint?
Multiple defense layers: the endpoint binds only to 127.0.0.1, uses a 256-bit random path secret compared in constant time, accepts exactly one concurrent connection, and validates the Host header. These constraints make remote exploitation computationally infeasible even if the port number is discovered.
How does the computer-use model recover when Chrome DevTools is opened?
Opening DevTools triggers the debugger module's single-attacher limit, causing CdpBridge to emit a target-busy error. The runtime in packages/runtime/src/computer-use-tools.ts catches this, applies exponential backoff retry, and eventually surfaces a user-facing message if the conflict persists.
Can the CDP bridge be disabled or replaced with a remote endpoint?
Yes. The configuration in packages/core/src/computer-use.ts accepts a cdpPort override, and the protocol abstraction in maka-cu-protocol.ts permits alternative implementations. However, the default desktop build expects the local bridge for security isolation.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →