Skip to content

Conversation

@pcarleton
Copy link
Member

Summary

Add conformance tests to verify localhost MCP servers properly validate Host headers to prevent DNS rebinding attacks.

Closes #103

Changes

New scenario: dns-rebinding-protection

Two checks:

  • localhost-host-rebinding-rejected: Server returns 4xx for non-localhost Host headers (e.g., Host: evil.example.com)
  • localhost-host-valid-accepted: Server accepts valid localhost Host headers

Implementation details

  • Uses undici for making HTTP requests with custom Host headers (native fetch doesn't allow this)
  • Accepts any 4xx status code as valid rejection (403, 421, etc.)
  • Tests require localhost server URL - non-localhost URLs return FAILURE with clear message

Additional changes

  • Updated everything-server example to use createMcpExpressApp() for DNS rebinding protection
  • Added negative test case no-dns-rebinding-protection.ts that intentionally omits protection

Testing

Verified against:

  • ✅ TypeScript SDK (everything-server with createMcpExpressApp())
  • ✅ Python SDK (everything-server with auto-enabled protection)
  • ✅ Negative test case (correctly fails)

Related

pcarleton and others added 3 commits January 22, 2026 14:18
Add a new server conformance scenario to verify that localhost MCP servers
properly validate Host headers to prevent DNS rebinding attacks.

Checks:
- localhost-host-rebinding-rejected: Verifies server returns 403 for
  non-localhost Host headers (e.g., evil.example.com)
- localhost-host-valid-accepted: Verifies server accepts requests with
  valid localhost Host headers

Also updates the everything-server example to use createMcpExpressApp()
which includes DNS rebinding protection by default.

Closes #103

Co-Authored-By: Claude <noreply@anthropic.com>
Add a minimal MCP server that intentionally omits DNS rebinding protection
to serve as a negative test case. This server is expected to FAIL the
dns-rebinding-protection scenario.

Also update README to document the negative test case.

Co-Authored-By: Claude <noreply@anthropic.com>
Update the DNS rebinding protection test to accept any 4xx status code
as a valid rejection response, not just 403. This accommodates different
HTTP semantics:
- 403 Forbidden: Per MCP spec
- 421 Misdirected Request: RFC 7540 (semantically correct for DNS rebinding)
- Other 4xx: Implementation-specific error codes
@pkg-pr-new
Copy link

pkg-pr-new bot commented Jan 22, 2026

Open in StackBlitz

npx https://pkg.pr.new/modelcontextprotocol/conformance/@modelcontextprotocol/conformance@115

commit: 7c1b3db

EventId,
StreamId
} from '@modelcontextprotocol/sdk/server/streamableHttp.js';
import { createMcpExpressApp } from '@modelcontextprotocol/sdk/server/express.js';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note: this example is going to get deleted in a follow-up PR to make the typescript-sdk example the source of truth. updating it in place for now.

- Simplify negative test server (remove event store, sessions)
- Update spec reference URL to security_best_practices
- Clarify scope: localhost servers without HTTPS/auth
- DRY up checks.push using spread operator
- Fix valid host check to require 2xx response
@pcarleton pcarleton marked this pull request as ready for review January 22, 2026 15:49
…tion

- Send both Host and Origin headers in test requests so servers checking
  either header will pass the conformance test
- Add second spec reference for Origin check (transports#security-warning)
- Update descriptions to mention both Host and Origin headers
- Fix protocol version to 2025-11-25
- Remove unnecessary 'as const' type assertions
@pcarleton pcarleton requested a review from maxisbey January 22, 2026 18:18
Comment on lines 207 to 208
hostHeader: validHost,
originHeader: `http://${validHost}`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we should actually be testing these separately? Otherwise we don't know whether the server will reject both a bad host and a bad origin, we only know that it will reject either:

  1. both
  2. only header
  3. only origin

but not which specifically

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's intentional, we want either protection to be valid. Checking host or origin will prevent the attack.

@pcarleton pcarleton requested a review from maxisbey January 22, 2026 18:31
@pcarleton pcarleton merged commit d0b2b49 into main Jan 22, 2026
8 checks passed
@pcarleton pcarleton deleted the feat/dns-rebinding-tests branch January 22, 2026 18:37
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.

[Tiering] Add conformance tests for localhost DNS rebinding protection

3 participants