Merge commit from fork
Some checks failed
Main branch checks / checks (push) Has been cancelled

* Auto-enable DNS rebinding protection for localhost servers

When a FastMCP server is created with host="127.0.0.1" or "localhost"
and no explicit transport_security is provided, automatically enable
DNS rebinding protection. Both 127.0.0.1 and localhost are allowed
as valid hosts/origins since clients may use either to connect.

* Add tests for auto DNS rebinding protection on localhost

Tests verify that:
- Protection auto-enables for host=127.0.0.1
- Protection auto-enables for host=localhost
- Both 127.0.0.1 and localhost are in allowed hosts/origins
- Protection does NOT auto-enable for other hosts (e.g., 0.0.0.0)
- Explicit transport_security settings are not overridden

* Add IPv6 localhost (::1) support for DNS rebinding protection

Extend auto-enable DNS rebinding protection to also cover IPv6
localhost. When host="::1", protection is now auto-enabled with
appropriate allowed hosts ([::1]:*) and origins (http://[::1]:*).

* Fix import ordering in test file
This commit is contained in:
Paul Carleton 2025-12-02 13:23:55 +00:00 • committed by GitHub
commit d3a184119e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 55 additions and 0 deletions

View file

@ -174,6 +174,14 @@ class FastMCP(Generic[LifespanResultT]):
auth: AuthSettings | None = None,
transport_security: TransportSecuritySettings | None = None,
):
# Auto-enable DNS rebinding protection for localhost (IPv4 and IPv6)
if transport_security is None and host in ("127.0.0.1", "localhost", "::1"):
transport_security = TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
)
self.settings = Settings(
debug=debug,
log_level=log_level,

View file

@ -12,6 +12,7 @@ from mcp.server.fastmcp.prompts.base import Message, UserMessage
from mcp.server.fastmcp.resources import FileResource, FunctionResource
from mcp.server.fastmcp.utilities.types import Audio, Image
from mcp.server.session import ServerSession
from mcp.server.transport_security import TransportSecuritySettings
from mcp.shared.exceptions import McpError
from mcp.shared.memory import (
create_connected_server_and_client_session as client_session,
@ -183,6 +184,52 @@ class TestServer:
return f"Data: {x}"
class TestDnsRebindingProtection:
"""Tests for automatic DNS rebinding protection on localhost."""
def test_auto_enabled_for_127_0_0_1(self):
"""DNS rebinding protection should auto-enable for host=127.0.0.1."""
mcp = FastMCP(host="127.0.0.1")
assert mcp.settings.transport_security is not None
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
assert "127.0.0.1:*" in mcp.settings.transport_security.allowed_hosts
assert "localhost:*" in mcp.settings.transport_security.allowed_hosts
assert "http://127.0.0.1:*" in mcp.settings.transport_security.allowed_origins
assert "http://localhost:*" in mcp.settings.transport_security.allowed_origins
def test_auto_enabled_for_localhost(self):
"""DNS rebinding protection should auto-enable for host=localhost."""
mcp = FastMCP(host="localhost")
assert mcp.settings.transport_security is not None
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
assert "127.0.0.1:*" in mcp.settings.transport_security.allowed_hosts
assert "localhost:*" in mcp.settings.transport_security.allowed_hosts
def test_auto_enabled_for_ipv6_localhost(self):
"""DNS rebinding protection should auto-enable for host=::1 (IPv6 localhost)."""
mcp = FastMCP(host="::1")
assert mcp.settings.transport_security is not None
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
assert "[::1]:*" in mcp.settings.transport_security.allowed_hosts
assert "http://[::1]:*" in mcp.settings.transport_security.allowed_origins
def test_not_auto_enabled_for_other_hosts(self):
"""DNS rebinding protection should NOT auto-enable for other hosts."""
mcp = FastMCP(host="0.0.0.0")
assert mcp.settings.transport_security is None
def test_explicit_settings_not_overridden(self):
"""Explicit transport_security settings should not be overridden."""
custom_settings = TransportSecuritySettings(
enable_dns_rebinding_protection=False,
)
mcp = FastMCP(host="127.0.0.1", transport_security=custom_settings)
# Settings are copied by pydantic, so check values not identity
assert mcp.settings.transport_security is not None
assert mcp.settings.transport_security.enable_dns_rebinding_protection is False
assert mcp.settings.transport_security.allowed_hosts == []
def tool_fn(x: int, y: int) -> int:
return x + y