Browser Proxy - Z-M-Huang/openhive GitHub Wiki

Browser Proxy

OpenHive provides browser automation through @playwright/mcp via inline browser tool definitions. Each tool's execute() function calls the BrowserRelay directly, with centralized audit logging via withAudit() wrappers, team-level gating, and SSRF protection with optional domain allowlisting on browser_navigate.

Architecture

flowchart TD
    A["Team Session (web-team)"] -->|"browser_navigate({ url })"| B["browser tool execute()<br/>(browser-tools.ts)"]
    B -->|"1. Gate: browser config exists?<br/>2. validateBrowserUrl()<br/>3. relay.callTool()"| C["BrowserRelay<br/>(browser-proxy.ts)"]
    C -->|"MCP Client / StdioClientTransport"| D["@playwright/mcp<br/>(child process, --headless)"]
    D --> E["Chromium"]
Loading

BrowserRelay Lifecycle

The BrowserRelay manages a single @playwright/mcp child process:

Phase Behavior
Startup initBrowserRelay() runs in the background (non-blocking). Resolves @playwright/mcp/package.jsoncli.js. Spawns via StdioClientTransport. Enumerates tools via client.listTools().
Active callTool() forwards calls to the MCP client. Updates lastUsedAt timestamp on each call.
Idle cleanup A 60-second interval checks Date.now() - lastUsedAt > idleTtlMs (default 5 min). On timeout, disconnects the MCP client and kills the child process.
Reconnect After idle cleanup, the next callTool() spawns a new child process and reconnects. available remains true throughout.
Shutdown browserRelay.close() kills the child process and clears the idle timer. Called during graceful shutdown.
Init failure If @playwright/mcp is not installed or Chromium is missing, logs an error and returns undefined. Server continues without browser capability.

Connection Timeout

The initial client.connect() has a 2-second timeout. If the MCP handshake doesn't complete (e.g., Chromium can't launch), the transport is closed (fire-and-forget) and the error propagates to initBrowserRelay which logs and returns undefined.

Chromium Resolution

The relay auto-detects the Playwright-bundled Chromium binary from PLAYWRIGHT_BROWSERS_PATH:

If found, the relay passes --executable-path to the CLI pointing to the Playwright-bundled Chromium binary under PLAYWRIGHT_BROWSERS_PATH. This avoids the default behavior of looking for Google Chrome at a system path.

Tool Registration

Browser tools are conditionally registered in buildBrowserTools(ctx: OrgToolContext):

Browser tools are conditionally included when the BrowserRelay reports itself as available. This means:

  • No @playwright/mcp installed → no browser tools available
  • Relay available but team has no browser: config → tools exist but return error on call (Gate 1)
  • Relay available and team has browser: config → tools work

Available Tools

Tool Purpose URL Validation?
browser_navigate Navigate to a URL Yes — checked against allowed_domains
browser_snapshot Accessibility snapshot (YAML tree) No
browser_screenshot Visual screenshot (ImageContent) No
browser_click Click an element by text or ref No
browser_type Type into an element No
browser_go_back Browser history back No
browser_go_forward Browser history forward No
browser_close Close the browser tab No

Web Fetch Tool

The web_fetch tool provides lightweight HTTP GET/POST without launching Playwright. It reuses validateBrowserUrl() for SSRF protection and respects the team's domain allowlist.

Parameter Required Description
url Yes Target URL (validated against SSRF rules and domain allowlist)
method No GET (default) or POST
headers No Key-value map of request headers
body No Request body (for POST requests)

Returns: { status, headers, body } — the HTTP status code, response headers, and response body as text.

Availability: Any team can use web_fetch by including it in allowed_tools. It does not require browser: config since it does not use Playwright or Chromium.

SSRF Protection

The validateBrowserUrl() function in url-validator.ts enforces a hard security layer that blocks private/reserved addresses regardless of domain allowlist configuration:

Blocked Range Examples
Loopback 127.0.0.1, localhost, ::1
Link-local 169.254.x.x (includes AWS/GCP metadata 169.254.169.254)
RFC 1918 private 10.x.x.x, 172.16-31.x.x, 192.168.x.x
RFC 6598 shared 100.64-127.x.x
IPv6 private fe80::, fc00::, fd00::
Null address 0.0.0.0

This validation runs before domain allowlist checking and cannot be bypassed. It prevents browser tools from being used for SSRF attacks against internal services, cloud metadata endpoints, or other private network resources.

Domain Allowlist

The validateBrowserUrl() function also checks the URL hostname against the team's browser.allowed_domains:

  • No allowlist (omitted or empty): All public URLs allowed
  • Exact match: example.com matches example.com
  • Glob match: *.example.com matches sub.example.com but not example.com itself
  • Scheme restrictions: only http: and https: are permitted (url-validator.ts rejects all other schemes including ftp:)

Domain validation runs only on browser_navigate. Other tools (snapshot, screenshot, click, etc.) operate on the already-navigated page.

Audit Logging

Each browser tool is wrapped with withAudit() (tool-audit.ts), which logs tool call start/end events with tool name, callerId, duration, and outcome. The wrapper also handles credential scrubbing before persisting audit records.

Configuration

Add to a team's config.yaml:

Browser configuration in config.yaml supports an optional allowed_domains list (for restricting navigable URLs) and an optional timeout_ms (default 30000, accepted in schema but not yet consumed by browser tools). See Team-Configuration#Browser Configuration for the full config format.

The browser config schema accepts an optional allowed_domains string array and an optional timeout_ms number. Both fields are validated by Zod at load time.

Dependencies

  • @playwright/mcp — MCP server for Playwright browser automation (required)
  • playwright — Provides Chromium binary and CLI (required, explicit dependency for bun compatibility)
  • @modelcontextprotocol/sdk — MCP Client + StdioClientTransport (already a dependency)
  • Chromium runtime libraries — installed in the Docker image via npx playwright install --with-deps chromium
⚠️ **GitHub.com Fallback** ⚠️