Securing a local MCP server: the Origin check, the Host check and DNS rebinding

Binding an MCP server to localhost doesn't keep web pages out. A page can reach it through the browser, and DNS rebinding makes that worse. Three checks close it; here they are, tested.

The short answer

If your MCP server runs on your own machine, prefer the stdio transport, which the spec recommends for local servers because it limits access to the MCP client.3 If you must use HTTP, do three things: bind to 127.0.0.1, reject requests whose Origin header isn’t yours with HTTP 403, and require a token. The MCP spec says servers must validate Origin on all incoming connections to prevent DNS rebinding, must answer 403 when it is present and invalid, should bind only to localhost when running locally, and should authenticate all connections.2 We add a fourth: reject a Host header that isn’t localhost or 127.0.0.1.

Every MCP tool call goes through your rules first. Here a delete that doesn’t say which rows is stopped before it reaches the database. These are the rules deedglass init starts with; the call is made up.

Why localhost isn’t private

127.0.0.1 is unreachable from other machines. It is reachable from every program on your machine, and that includes your browser, which runs code from every web page you open.

Two routes from a web page to your local server:

  1. A plain cross-origin request. A page on evil.example can send a POST to http://127.0.0.1:8765/mcp. The browser may block the page from reading the response, but for many request shapes the request still arrives and your server still acts on it. For a tool call, acting is the damage.
  2. DNS rebinding. The page’s domain first resolves to the attacker’s server, then is switched to resolve to 127.0.0.1. Now the browser thinks requests to evil.example:8765 are same-origin, and the page can read responses too. Your server sees Host: evil.example:8765.

The spec puts it directly: without these protections, attackers could use DNS rebinding to interact with local MCP servers from remote websites.2

What the spec asks for

Check Spec wording Why
Validate Origin MUST, on all incoming connections; 403 if present and invalid21 Browsers attach Origin to cross-origin requests; scripts on a page can’t forge it
Bind to localhost SHOULD, rather than all interfaces (0.0.0.0)2 Keeps other machines on the network out
Authenticate SHOULD, for all connections2 Stops other local processes and anything that slips past the browser checks
Prefer stdio, or restrict HTTP SHOULD use stdio; with HTTP, require a token or use unix sockets or other restricted IPC3 No network listener at all

The Host check is our addition. It isn’t in the transport’s security warning, but it’s the standard defence against rebinding: after rebinding, the Host header carries the attacker’s name, not localhost.

A 20-line server with all three checks

Node’s standard library only, no framework:

Code Program code, for developers. The text around it says what it does.

import http from 'node:http';
const PORT = 8765;
const ALLOWED_ORIGINS = new Set([`http://localhost:${PORT}`, `http://127.0.0.1:${PORT}`]);
const ALLOWED_HOSTS = new Set([`localhost:${PORT}`, `127.0.0.1:${PORT}`]);
const TOKEN = process.env.MCP_TOKEN;
http.createServer((req, res) => {
  const origin = req.headers.origin;
  if (origin !== undefined && !ALLOWED_ORIGINS.has(origin)) { res.writeHead(403); return res.end('origin refused\n'); }
  if (!ALLOWED_HOSTS.has(req.headers.host ?? '')) { res.writeHead(421); return res.end('host refused\n'); }
  if (req.headers.authorization !== `Bearer ${TOKEN}`) { res.writeHead(401); return res.end('token required\n'); }
  res.writeHead(200, { 'content-type': 'application/json' });
  res.end('{"jsonrpc":"2.0","id":1,"result":{}}\n');
}).listen(PORT, '127.0.0.1');

Three choices worth copying:

  • A missing Origin passes the first check. Non-browser clients such as your MCP client don’t send one. The token check still applies to them.
  • The Host check returns 421 (Misdirected Request). Any 4xx works; 421 says what happened.
  • The token comes from the environment, generated at startup and handed to the client in its config. A fixed token in the source is no token.

What we saw with curl

We started the server with MCP_TOKEN=s3cret and sent five requests:

Code What the screen or file shows, copied as is.

1 no Origin, token        200
2 evil Origin, token      403
3 rebound Host, token     421
4 no token                401
5 local Origin, token     200

Request 2 is what a cross-origin page sends. Request 3 is what a rebound page sends. Request 4 is any other local process without the token. Only the legitimate client (1) and a page on the server’s own origin (5) got through.

lsof confirmed the listener was on 127.0.0.1:8765, not *:8765.

A real case: MCP Inspector

The MCP Inspector, the project’s own debugging tool, shipped a critical flaw in its local proxy. GitHub advisory GHSA-7f8r-222p-6f5g (CVE-2025-49596, CVSS 9.4) says versions below 0.14.1 were vulnerable to remote code execution because of a lack of authentication between the Inspector client and proxy, which allowed unauthenticated requests to launch MCP commands over stdio. The fix was version 0.14.1.4

The lesson generalises. A local process that can start other processes, and listens on a port without authentication, is a remote code execution waiting for a web page. If you run the Inspector, check its version. If you write a local MCP proxy, put the token check in from the first commit.

Where the checks run out

These checks protect the listener. They don’t help with three neighbouring problems:

  • A malicious server you installed. If the server itself is hostile, the Origin check protects its port, not you. The spec lists malicious startup commands in a client configuration and malicious payloads inside a server as separate attacks.3 Read what you install, and pin versions.
  • Other local software with your token. Anything that can read the client’s config file can read the token. Keep the token out of shared dotfiles and out of the repository.
  • Remote servers. A server on the internet needs real OAuth and audience checks, not a shared token; token passthrough and the confused deputy covers the spec’s rules for those.

One more habit helps: when you finish a debugging session, stop the local server. A listener that isn’t running can’t be reached by any page.

A checklist for local servers

  • Use stdio unless you have a reason for HTTP.
  • Bind to 127.0.0.1, never 0.0.0.0.
  • Reject a present, foreign Origin with 403.
  • Reject a Host that isn’t localhost or 127.0.0.1 with your port.
  • Require a random token, generated at startup, on every request.
  • Don’t enable permissive CORS (Access-Control-Allow-Origin: *) on an MCP endpoint.
  • Keep debugging tools such as the Inspector updated, and stop them when you’re done.
  • If a client offers one-click install of a local server, read the full command first; the spec requires clients to show it untruncated.3

Transport checks keep strangers out; they don’t limit what a connected agent may do. For that, see MCP server security basics and the MCP gateway entry. To grade what the servers configured on your machine can change, paste the config into the check page or run npx deedglass scan (how the grade works); to try the checks in your browser first, open the playground.

Written with AI assistance and checked against our claims register.

The takeaway

Our 20-line local server answered 403 to a foreign Origin, 421 to a rebound Host and 401 without a token. The MCP spec requires the Origin check.

Read nextMCP token passthrough and the confused deputy problem, explained Try it: install in 7 s

Related on this site

Terms

In the docs

More posts

Sources

  1. 1MCP specification: Streamable HTTP transport · modelcontextprotocol.io · checked 14 October 2026
  2. 2MCP specification (2025-11-25): Transports · modelcontextprotocol.io · checked 26 October 2026
  3. 3MCP specification: Security best practices · modelcontextprotocol.io · checked 26 October 2026
  4. 4GitHub advisory GHSA-7f8r-222p-6f5g: Inspector proxy server vulnerabilities · github.com · checked 26 October 2026

Published 26 October 2026, sources last checked 26 October 2026. If something here is out of date, write to hello@deedglass.com.

Try it on your own setup

npx deedglass scan

Grades only your own setup. Nothing is uploaded.

Pricing