Require auth for HTTP MCP transport on non-loopback hosts (CWE-319)#182
Open
sebastiondev wants to merge 1 commit into
Open
Require auth for HTTP MCP transport on non-loopback hosts (CWE-319)#182sebastiondev wants to merge 1 commit into
sebastiondev wants to merge 1 commit into
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The HTTP transport (
startHttpTransportinsrc/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.tsL50-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.src/transport.ts→startHttpTransportand the per-request handler.http.createServerhandler →/mcp→StreamableHTTPServerTransport→ 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:
There were no prior auth checks, no rate limiting, no allow-list, and no validation on the
/mcppath before this change.Fix
Minimal change scoped to
src/transport.ts(76 lines added, 0 removed):hostnameis anything other thanlocalhost/127.0.0.1/::1/::ffff:127.0.0.1andMCP_AUTH_TOKENis not set, the process logs an explanatory error and exits with code 1 instead of starting an open server.MCP_AUTH_TOKENis set, every request to the HTTP server must presentAuthorization: Bearer <token>. Missing or mismatched tokens get a401with aWWW-Authenticate: Bearer realm="mcp"header.crypto.timingSafeEqualover equal-length buffers to avoid timing side-channels.Authorizationheader 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
startHttpTransportis the only HTTP server path in the codebase (src/program.tsis the sole caller, and it forwards the raw--hostCLI option). There is no parallel unauthenticated route to bypass.handleStreamable, so session creation cannot happen pre-auth.--host localhost/ default bind / explicit IPv4 and IPv6 loopback.crypto.timingSafeEqualis 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.0is 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
MCP_AUTH_TOKEN. Suggested doc snippet: "When running the HTTP transport on a non-loopback interface, setMCP_AUTH_TOKENto a strong random secret and passAuthorization: Bearer <token>from clients."node:cryptomodule.--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_TOKENvs. something project-specific), split the warning out, or extend with allow-listed CIDR support if you'd prefer that ergonomics.cc @lewiswigmore