DNS rebinding in local MCP servers: how to check and fix it

A local MCP server that speaks HTTP (streamable HTTP or SSE) and does not check the Host header can be reached by any web page you open. The page does not need to break out of the browser: it makes the browser treat your server as part of the page's own site. This page shows how that works, a one-line check and the fixes. Last checked: 12 October 2026, against the MCP TypeScript SDK 1.26.0.

How the attack works

  1. You run an MCP server on your machine, for example on http://127.0.0.1:3000/mcp.
  2. You open a web page on a domain the attacker controls, say rebind.example. Its DNS record has a very short lifetime.
  3. After the page loads, the attacker changes the record so rebind.example points at 127.0.0.1.
  4. Script on the page now calls http://rebind.example:3000/mcp. The browser sees the same origin as the page, so the same-origin policy does not stop it and no CORS check applies. The request reaches your server.
  5. The one thing that gives it away is the Host header: it says rebind.example:3000, not 127.0.0.1:3000 or localhost:3000.

If the server ignores Host, the page can start a session, list the tools and call them, with whatever access those tools have: files, shell commands, tokens in the server's environment. Stdio servers are not exposed this way, since they have no port. Recent Chrome versions ask before a public site reaches a local address, but do not count on every browser, or every user, saying no.

How to check your server

Send a request with a Host header your server should never accept:

curl -i -H 'Host: rebind.example' http://127.0.0.1:3000/mcp

Use your own port and path. A server that checks Host refuses before it reads the request: the TypeScript SDK answers 403 with Invalid Host header, and the Python SDK's transport security answers 421. Any other status (200, or a 400, 405 or 406 complaining about the request) means the Host header was not checked.

Check the Origin header too. A browser sends Origin on these requests, so a server that checks it can refuse a page it does not know:

curl -i -H 'Origin: http://rebind.example' http://127.0.0.1:3000/mcp

With the protection on and an Origin allow-list set, the TypeScript SDK answers 403 with Invalid Origin header. Origin is a second line, not a replacement: requests that do not come from a browser can leave it out.

Also check where the server listens. lsof -iTCP -sTCP:LISTEN -n -P (macOS, Linux) shows each port and its address; 0.0.0.0 or * means every network interface, not just this machine.

How to fix it

new StreamableHTTPServerTransport({
  sessionIdGenerator: () => randomUUID(),
  enableDnsRebindingProtection: true,
  allowedHosts: ['127.0.0.1:3000', 'localhost:3000'],
  allowedOrigins: ['http://localhost:3000'],
});

A token or session secret on the server helps as well, but it is not a substitute: if the page can read a response that contains it, the secret is gone.

Lint your configs for it

The free MCP Safety Lint 1.0.1 (SHA-256) has a lint_config tool whose dns-rebinding rule flags an HTTP or SSE server config with no Host allow-list or with the protection turned off, and whose all-interfaces rule flags a server listening on 0.0.0.0; it runs offline, and the tool-poisoning guide shows how to add it.

Related: Hostile clients and L402 MCP gates, MCP conformance vs safety and testing MCP clients against tool poisoning.