Execution bridge

The native bridge daemon (openwebide-bridge) runs on the host to provide interactive PTY terminals, process execution (POST /exec), and host Git operations for the web frontend and coding agents. The backend reaches the bridge at 127.0.0.1:3001 and sends project-relative cwd.

[!NOTE] Git operations performed by the bridge require Git ≥ 2.23 on the host machine for branch switching via git switch.

The bridge's HTTP/1 and WebSocket server (built on hyper) enforces request limits: a 16 KiB HTTP request head, a 1 MiB /exec/Git request body, a 16 MiB WebSocket message/frame, and 256 concurrent connections. Idle sockets are closed after 10 s without a request head; WebSocket connections are pinged every 30 s and closed after 90 s without an inbound frame.

Running the bridge

cargo build -p openwebide-bridge
./target/debug/openwebide-bridge --port 3001 --workspace ../.. --host 127.0.0.1 \
  --backend-url http://127.0.0.1:3000/api --secret-file /tmp/openwebide-bridge-secret

Signed-in connections (hello with a bridge token) can run chat and agents over the bridge when it shares the backend's secret. Runs continue after a browser disconnect and can replay their events on reconnect. Otherwise, runs use SSE and completions use /api/chat-tools. HTTP-only builds (cargo build -p openwebide-bridge --no-default-features) advertise no run support; terminal and Git operations remain available.

Chat over the bridge: the frontend uses WebSocket runs after hello_ok advertises run support. In browser DevTools, prompts send run_start and replies arrive as run_event frames without an SSE request. Local folders keep their agent loop in the browser and stream model completions through completion_start. Reloading a running session attaches its snapshot; reconnecting resumes from the last sequence. If the bridge is unavailable, rejects authentication, or doesn't finish hello within about two seconds, chat uses SSE and local completions use /api/chat-tools. A project the bridge cannot see shows an info notice before server fallback; busy sessions and planning failures show errors. LAN clients use the same flow.

[!WARNING] When the backend reaches a remote bridge through a non-loopback URL, set both SPIN_VARIABLE_BRIDGE_URL=http://<bridge-host>:3001 and SPIN_VARIABLE_BRIDGE_SECRET to the bridge secret from its secret file or OPENWEBIDE_BRIDGE_SECRET. The daemon logs the secret source, never the secret itself. Binding to 0.0.0.0 still permits automatic bootstrap when the backend connects over loopback.

Host and Origin security baseline

To protect against DNS rebinding and malicious websites opened in the user's browser, the bridge validates incoming HTTP and WebSocket requests:

  1. Host Header Rules:
  2. Allowed if the Host is an IP literal (IPv4 or IPv6, e.g. 127.0.0.1, [::1], 192.168.1.50).
  3. Allowed if localhost, this machine's hostname (e.g. mymachine), or <hostname>.local.
  4. Allowed if explicitly added via --allowed-host <HOSTNAME> (or OPENWEBIDE_BRIDGE_ALLOWED_HOSTS comma-separated list).
  5. Any unrecognized or rebinding domain name is rejected with 403 Forbidden.

  6. Origin & CORS Rules:

  7. Requests without an Origin header (such as Spin backend calls, curl, and local daemon tools) are permitted, but API routes require authorization via Authorization: Bearer <SECRET>. For example: sh curl -X POST http://127.0.0.1:3001/exec \ -H "Authorization: Bearer <your-secret>" \ -H "Content-Type: application/json" \ -d '{"command": "echo test"}'
  8. Browser requests with an Origin header are permitted only if:
    • The origin is in the allowed origins list (default: http://localhost:3000, http://127.0.0.1:3000, http://localhost:8080, http://127.0.0.1:8080, plus any --allowed-origin entries); or
    • The origin's hostname matches the request's Host hostname (allowing phone/LAN access when the frontend and bridge are accessed on the same host machine).
  9. Origin: null and untrusted cross-origin requests are rejected with 403 Forbidden.
  10. Wildcard Access-Control-Allow-Origin: * is disabled; allowed origins receive their exact origin echoed with Vary: Origin.

  11. JSON-Only Browser POSTs:

  12. Browser POST requests carrying an Origin header require Content-Type: application/json; simple browser requests (e.g. text/plain, form-urlencoded) are rejected with 415 Unsupported Media Type to prevent browser CSRF.

Process lifecycle

Every command the bridge spawns (/exec, run_command, PTY shells, Git subprocesses) starts in its own process group. Signals target that group; interactive shells can put background jobs into separate groups, which may survive shell cleanup:

Back to the README.