Browser Proxy - Z-M-Huang/openhive GitHub Wiki
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.
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"]
The BrowserRelay manages a single @playwright/mcp child process:
| Phase | Behavior |
|---|---|
| Startup |
initBrowserRelay() runs in the background (non-blocking). Resolves @playwright/mcp/package.json → cli.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. |
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.
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.
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/mcpinstalled → 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
| 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 |
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.
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.
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.commatchesexample.com -
Glob match:
*.example.commatchessub.example.combut notexample.comitself -
Scheme restrictions: only
http:andhttps:are permitted (url-validator.ts rejects all other schemes includingftp:)
Domain validation runs only on browser_navigate. Other tools (snapshot, screenshot, click, etc.) operate on the already-navigated page.
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.
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.
-
@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