DNS Rebinding and Localhost MCP: Your Dev Server is a Production Attack Vector

Written by the Rafter Team

On December 2, 2025, a security advisory disclosed a critical vulnerability in the Model Context Protocol TypeScript SDK. The bug—tracked as GHSA-w48q-cv73-mx4w / CVE-2025-66414 (CVSS 7.6)—allowed malicious websites to send arbitrary requests to MCP servers running on localhost. No browser warning. No CORS error. Just silent access to your filesystem, databases, and APIs through the MCP tools you run locally for convenience.
The attack vector was DNS rebinding, a technique that weaponizes the gap between DNS resolution and browser security policy. Before SDK version 1.24.0, every HTTP-based MCP server was vulnerable by default. If you ran MCP on 127.0.0.1 without authentication—the standard developer setup—any website you visited could become an attack platform. The vulnerability was reported by pcarleton.
Why Developers Run MCP on Localhost
Model Context Protocol connects AI assistants to external tools: filesystems, Git repositories, databases, Slack APIs. The protocol is transport-agnostic, supporting stdio (process pipes), HTTP (local server), and SSE (server-sent events).
For solo developers, localhost HTTP is the path of least resistance. You start an MCP server on http://127.0.0.1:3000, point Claude Desktop or your IDE at it, and immediately gain access to powerful tools—file search, code execution, data queries—without configuring TLS certificates or authentication. The MCP specification supports stdio, HTTP, and SSE transports—but HTTP on localhost is the default for many implementations.
The threat model assumed localhost was trustworthy. Browsers enforce same-origin policy: https://evil.com can't make requests to http://127.0.0.1:3000. Network firewalls don't matter because traffic never leaves your machine. External attackers can't reach 127.0.0.1 by definition.
That assumption breaks under DNS rebinding.
DNS Rebinding Primer
DNS rebinding exploits the time gap between DNS resolution and browser security checks. Here's how same-origin policy normally works:
- You visit
https://example.com - Browser resolves
example.com→93.184.216.34 - JavaScript on that page tries to fetch
http://internal-server.local - Browser blocks the request: cross-origin violation
DNS rebinding introduces a timing attack:
- Attacker controls
evil.comand its authoritative DNS server - You visit
https://evil.com - Browser resolves
evil.com→93.184.216.34(attacker's public IP) - Page loads, JavaScript runs
- Attacker's DNS server now returns
127.0.0.1forevil.com(TTL=0) - JavaScript fetches
http://evil.com:3000/mcp/tool/call - Browser sees same origin (
evil.com→evil.com), allows request - Request goes to
127.0.0.1:3000—your MCP server
The browser's origin check happens before DNS resolution. By the time the TCP connection reaches localhost, the security decision has already been made.
Modern browsers have partial mitigations—blocking localhost resolution for public domains, enforcing longer DNS TTLs—but these are not universal and vary across browsers, platforms, and network configurations. DNS rebinding remains viable against localhost services that don't implement explicit Host header validation or authentication.
Attack Flow: From Website Visit to File Exfiltration
Let's trace a realistic attack against a developer running the Anthropic filesystem MCP server on http://127.0.0.1:3000.
Phase 1: Initial Compromise
The victim visits https://attacker.com, which serves an innocuous page—maybe a developer blog, npm package documentation, or a GitHub Pages site. The page loads normally. No malware download, no browser warning.
Phase 2: DNS Manipulation
The attacker controls the authoritative DNS for attacker.com. Initial resolution returns the attacker's public IP (93.184.216.34), allowing the page to load. JavaScript on the page then initiates a background fetch to http://attacker.com:3000.
Before the browser makes that request, it re-resolves attacker.com. The attacker's DNS server now returns 127.0.0.1 with a 0-second TTL. The browser cache is bypassed.
Phase 3: MCP Tool Invocation
The JavaScript constructs a JSON-RPC request targeting the filesystem MCP server:
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "read_file",
"arguments": {
"path": "/Users/victim/.ssh/id_rsa"
}
},
"id": 1
}
The request goes to 127.0.0.1:3000. The MCP server—expecting only trusted local clients—processes it without authentication. The SSH private key is returned in the response.
Phase 4: Exfiltration
The JavaScript reads the response and exfiltrates it via:
fetch()POST to attacker's logging server- WebSocket connection
- DNS tunneling via subdomain queries
The victim sees nothing. No console errors, no permission prompts, no file access notifications.
Sequence Diagram
Victim Browser Attacker DNS MCP (localhost:3000)
| | | |
|--Visit evil.com------>| | |
| |--Resolve evil.com--->| |
| |<--93.184.216.34------| |
| |--Load page---------->| |
|<--Page loaded---------| | |
| | | |
| |--Resolve evil.com--->| |
| |<--127.0.0.1----------| |
| |--POST /mcp/tool---------------------->| |
| | | | |
| |<--SSH key contents--------------------------|
| |--Exfil to attacker-->| |
Case Study: Filesystem MCP Under Attack
The Anthropic filesystem MCP server exposes five tools:
read_file: Read arbitrary filesread_multiple_files: Batch file readswrite_file: Write to any writable pathedit_file: In-place file modificationlist_directory: Enumerate directory contents
In a DNS rebinding attack, an attacker can:
Reconnaissance: Use list_directory to enumerate /Users/victim, identifying config files, credential stores, project directories.
Credential Theft: Use read_file to extract:
.ssh/id_rsa(SSH keys).aws/credentials(AWS keys).envfiles (API tokens)~/.config/*(application secrets)
Code Modification: Use write_file to inject backdoors into local projects:
- Add malicious dependencies to
package.json - Inject data exfiltration into build scripts
- Modify source files before commit
Persistence: Use edit_file to modify shell profiles (.bashrc, .zshrc), ensuring malicious code runs on every terminal session.
The attack surface is bounded only by the victim's filesystem permissions. If the MCP server runs with the developer's user privileges—the default—it has access to everything the developer can access.
The Fix: SDK v1.24.0 and DNS Rebinding Protection
Anthropic's fix landed in @modelcontextprotocol/sdk version 1.24.0. The patch adds Host header validation for HTTP transports:
Before (vulnerable):
server.listen(3000); // Accepts any Host header
After (hardened):
server.listen(3000, {
allowedOrigins: ['127.0.0.1', 'localhost'],
validateHost: true
});
The SDK now rejects requests where the Host header doesn't match the expected localhost values. Even if DNS rebinding tricks the browser into sending requests to 127.0.0.1, the MCP server sees Host: attacker.com and returns a 403.
The fix is enabled by default for new servers but requires explicit opt-in for existing deployments. Developers must upgrade to SDK 1.24.0 and ensure their server initialization includes the new options.
Migration checklist:
- Update
@modelcontextprotocol/sdkto ≥1.24.0 - Verify
validateHost: truein server config - Test that external Host headers are rejected
- Rotate any credentials that may have been exposed
Defense in Depth: Beyond the Patch
DNS rebinding protection is necessary but insufficient. Localhost services need defense in depth:
1. Mandatory Authentication
Even on localhost, require authentication. Options:
- API keys: Generate a random token on first run, require it in
Authorizationheader - mTLS: Mutual TLS with client certificates
- Unix domain sockets: Bypass TCP/IP entirely (stdio transport)
The MCP spec supports an optional authorization flow, but it's not enforced by default. Production deployments should treat it as mandatory.
2. Secure-by-Default Localhost Configuration
| Configuration | Vulnerable | Hardened |
|---|---|---|
| Transport | HTTP on 0.0.0.0 | Stdio or HTTP on 127.0.0.1 only |
| Authentication | None | Required (API key or mTLS) |
| Host validation | Disabled | Enabled (SDK 1.24.0+) |
| CORS | Access-Control-Allow-Origin: * | Explicit allowlist or disabled |
| TLS | Plaintext HTTP | TLS with self-signed cert minimum |
Binding to 0.0.0.0 exposes the service to your local network. Bind to 127.0.0.1 exclusively.
3. Principle of Least Privilege
Restrict MCP tools to the minimum required scope:
- Filesystem: Allowlist specific directories instead of full filesystem access
- Git: Use
--repositoryflag to restrict to approved repos - Database: Read-only credentials for query tools, separate write credentials
The Anthropic Git MCP server had three CVEs (CVE-2025-68143, 68144, 68145)—all stemming from insufficient input validation. Defense must assume tools will be invoked by adversaries.
4. Network-Level Controls
If you must run HTTP-based MCP:
- Firewall rules: Block external access to MCP ports
- DNS firewall: Use a DNS resolver that blocks rebinding attempts (e.g., Pi-hole with rebinding protection)
- Browser isolation: Run development browsers in VMs or containers
These are supplements, not substitutes, for application-level controls.
Rafter: Auditing Localhost Security at Scale
For individual developers, manually auditing localhost services is tractable. For organizations running MCP at scale—across hundreds of developers with heterogeneous tool stacks—it's a nightmare.
Rafter is building a localhost security audit scanner that:
- Discovers running MCP servers: Port scans localhost, identifies MCP endpoints via JSON-RPC probing
- Tests for DNS rebinding vulnerability: Simulates attack with crafted Host headers
- Validates authentication: Checks if servers accept unauthenticated requests
- Scans for CVEs: Matches MCP server fingerprints against known vulnerabilities (CVE-2025-68143/44/45)
- Generates remediation reports: Step-by-step fixes prioritized by risk
These are the kinds of checks we're building at Rafter—automated detection of insecure MCP configurations before they reach production. If you're running MCP in production, sign up at rafter.so to follow our progress.
Conclusion
DNS rebinding transforms localhost from a trusted development environment into a remotely exploitable attack surface. The MCP SDK patch closes one vector, but the broader lesson is that localhost is not a security boundary.
Every HTTP service on 127.0.0.1—MCP servers, local APIs, development databases—must assume hostile network access. Authentication is not optional. Host validation is not paranoia. Defense in depth is not overkill.
If you're running MCP on localhost:
- Upgrade to
@modelcontextprotocol/sdk≥1.24.0 immediately - Enable authentication for all HTTP transports
- Validate that Host header checks are active
- Audit your localhost services for similar vulnerabilities
The convenience of http://127.0.0.1:3000 is not worth SSH key exfiltration. Secure your localhost, or attackers will do it for you.
See also: In February 2026, Oasis Security disclosed ClawJacked (CVE-2026-25253) — a cross-origin WebSocket attack against OpenClaw's localhost gateway. Different technique (WebSocket vs. DNS rebinding), same trust model failure: local services that assume localhost connections are safe. The localhost trust assumption is under active attack from multiple directions.
Resources: