Skip to content

Harden HTTP transport: enable DNS-rebinding protection and default to loopback bind - #314

Open
winstoncrooker wants to merge 1 commit into
brave:mainfrom
winstoncrooker:harden-http-transport-dns-rebinding
Open

Harden HTTP transport: enable DNS-rebinding protection and default to loopback bind#314
winstoncrooker wants to merge 1 commit into
brave:mainfrom
winstoncrooker:harden-http-transport-dns-rebinding

Conversation

@winstoncrooker

Copy link
Copy Markdown

Harden the HTTP transport: enable DNS-rebinding protection and default to a loopback bind

Background

When the server runs with --transport http, the /mcp endpoint is unauthenticated and every tool call is proxied to api.search.brave.com with the configured X-Subscription-Token (src/BraveAPI/index.ts). So any party that can reach the listener can invoke search tools under the operator's Brave Search API key.

Two things make that reachable in the current defaults:

  1. No Host/Origin validation. The three StreamableHTTPServerTransport constructions in src/protocols/http.ts don't set enableDnsRebindingProtection/allowedHosts. Without it, a website the operator visits can use DNS rebinding to point its hostname at 127.0.0.1 and POST to http://localhost:<port>/mcp — the browser request is accepted. This is the exact attack enableDnsRebindingProtection exists to stop.
  2. 0.0.0.0 bind by default (src/config.ts). With the listener on all interfaces, any other host on the network (or a co-located process/container) can reach /mcp directly.

The pinned @modelcontextprotocol/sdk@1.29.0 already exposes enableDnsRebindingProtection / allowedHosts / allowedOrigins; they just weren't wired up. This aligns with the MCP transport-security guidance (validate Origin, bind to localhost, and authenticate local HTTP servers).

Change

  • src/protocols/http.ts: set enableDnsRebindingProtection: true and allowedHosts: config.allowedHosts on all three transports.
  • src/config.ts: default the bind host to 127.0.0.1, and build a Host allowlist that defaults to the bind host plus loopback. A new --allowed-hosts flag / BRAVE_MCP_ALLOWED_HOSTS env lets proxied/remote deployments (a gateway, or the stateless AgentCore path) add their public host.

Default/local usage is unaffected — loopback hosts are allowed. Deployments that intentionally expose the server keep doing so by setting --host/BRAVE_MCP_HOST and --allowed-hosts/BRAVE_MCP_ALLOWED_HOSTS.

Verification (before / after)

tools/list POST to /mcp with a spoofed Host header vs. the legitimate loopback host:

Host header before (main) after (this PR)
attacker.example (DNS-rebind / cross-host) HTTP 200 (accepted) HTTP 403 (rejected)
127.0.0.1:<port> (legitimate) HTTP 200 HTTP 200

Repro:

BRAVE_API_KEY=<key> node dist/index.js --transport http --port 8077 &
curl -s -o /dev/null -w "%{http_code}\n" \
  -H "Host: attacker.example" \
  -H "Accept: application/json, text/event-stream" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
  http://127.0.0.1:8077/mcp

References

  • MCP Transport Security (Origin validation / bind localhost / authentication for local Streamable-HTTP servers).
  • @modelcontextprotocol/sdk Streamable HTTP options: enableDnsRebindingProtection, allowedHosts, allowedOrigins.

… loopback bind

The --transport http /mcp endpoint is unauthenticated and proxies every tool
call with the configured Brave API key. Without Host/Origin validation a website
(via DNS rebinding to 127.0.0.1) or a cross-host party can reach it and run
searches under the operator's key. Enable the SDK's enableDnsRebindingProtection
+ allowedHosts on all transports and default the bind to loopback.
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