Skip to content

Require auth for HTTP MCP transport on non-loopback hosts (CWE-319)#182

Open
sebastiondev wants to merge 1 commit into
browserbase:mainfrom
sebastiondev:fix/cwe319-transport-http-9afa
Open

Require auth for HTTP MCP transport on non-loopback hosts (CWE-319)#182
sebastiondev wants to merge 1 commit into
browserbase:mainfrom
sebastiondev:fix/cwe319-transport-http-9afa

Conversation

@sebastiondev
Copy link
Copy Markdown

Summary

The HTTP transport (startHttpTransport in src/transport.ts) accepts MCP requests on the configured host:port without any authentication. When the server is bound to a non-loopback interface — which the README explicitly documents via --host 0.0.0.0 (README L209, program.ts L50-51) — anyone with network access to that port can drive the full MCP toolset, which in turn drives Stagehand/Browserbase browser automation backed by the operator's server-side API keys.

  • Weakness: CWE-319 (Cleartext / unauthenticated transmission of sensitive functionality), with elements of CWE-306 (missing authentication for a critical function).
  • Affected file/function: src/transport.tsstartHttpTransport and the per-request handler.
  • Data flow: unauthenticated TCP client → http.createServer handler → /mcpStreamableHTTPServerTransport → MCP tools that call out to Browserbase / the configured model provider using credentials held by the server process.

Impact

A pre-auth network attacker that can reach the bound port can:

  • Issue arbitrary browser-automation tool calls against the operator's Browserbase account (consuming session minutes, navigating to attacker-chosen URLs, exfiltrating page contents).
  • Use the browser as an SSRF primitive against internal hosts reachable from the Browserbase environment.
  • Burn the operator's model API quota by invoking Stagehand-backed tools.

There were no prior auth checks, no rate limiting, no allow-list, and no validation on the /mcp path before this change.

Fix

Minimal change scoped to src/transport.ts (76 lines added, 0 removed):

  1. Fail-closed on non-loopback bind without a token. If hostname is anything other than localhost / 127.0.0.1 / ::1 / ::ffff:127.0.0.1 and MCP_AUTH_TOKEN is not set, the process logs an explanatory error and exits with code 1 instead of starting an open server.
  2. Bearer token enforcement. When MCP_AUTH_TOKEN is set, every request to the HTTP server must present Authorization: Bearer <token>. Missing or mismatched tokens get a 401 with a WWW-Authenticate: Bearer realm="mcp" header.
  3. Constant-time comparison. Token comparison uses crypto.timingSafeEqual over equal-length buffers to avoid timing side-channels.
  4. Discoverability. The config snippet printed at startup includes the Authorization header placeholder when a token is configured, so operators see the right client config immediately.

Loopback-only deployments without a token continue to work unchanged, with a startup warning clarifying that only loopback connections are accepted. This preserves the existing local-dev UX while closing the network-exposed case.

What was tested

  • Built the TypeScript and reviewed the resulting handler logic.
  • Confirmed startHttpTransport is the only HTTP server path in the codebase (src/program.ts is the sole caller, and it forwards the raw --host CLI option). There is no parallel unauthenticated route to bypass.
  • Verified the request handler runs the auth check before dispatching to handleStreamable, so session creation cannot happen pre-auth.
  • Verified the loopback detection covers the values Node will actually report for --host localhost / default bind / explicit IPv4 and IPv6 loopback.
  • Verified crypto.timingSafeEqual is only invoked on equal-length buffers (length-mismatch returns false early), avoiding the runtime exception that function throws on unequal lengths.

Adversarial review

Before submitting, we tried hard to disprove this. We checked whether some other layer might already be authenticating requests (no — the handler dispatches straight to the streamable transport), whether the README/Dockerfile assume a reverse proxy in front (they don't — --host 0.0.0.0 is documented as a direct usage pattern), whether the preconditions for exploitation already grant equivalent access (they don't — pre-auth network reachability is a much weaker position than the code-execution-equivalent capability the MCP tools provide against the operator's Browserbase account), and whether a recent commit had already addressed this (it hadn't). The fix is the only thing standing between a network-reachable port and full use of the operator's credentials.

Notes for maintainers

  • New env var: MCP_AUTH_TOKEN. Suggested doc snippet: "When running the HTTP transport on a non-loopback interface, set MCP_AUTH_TOKEN to a strong random secret and pass Authorization: Bearer <token> from clients."
  • No dependencies added; uses the built-in node:crypto module.
  • Behavior on --host localhost (the default in the README's quick-start) is unchanged aside from a one-line startup warning.

Happy to adjust naming (MCP_AUTH_TOKEN vs. something project-specific), split the warning out, or extend with allow-listed CIDR support if you'd prefer that ergonomics.

cc @lewiswigmore

…k hosts

The HTTP transport bound to a configurable hostname (incl. 0.0.0.0) and
served the /mcp endpoint with no authentication. Anyone able to reach
the port could open new MCP sessions and drive browser automation,
indirectly using the configured Browserbase / model API keys (CWE-319).

This change:
- Reads MCP_AUTH_TOKEN from the environment.
- Refuses to start the HTTP transport on a non-loopback host unless
  MCP_AUTH_TOKEN is set, exiting with a clear error message.
- When MCP_AUTH_TOKEN is set, validates a 'Authorization: Bearer ...'
  header on every HTTP request using a constant-time comparison and
  returns 401 with WWW-Authenticate otherwise.
- Prints a warning when running on loopback without a token.
- Includes the Authorization header hint in the printed client config
  when a token is configured.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant