* 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:
parent
fa851d93a2
commit
d3a184119e
2 changed files with 55 additions and 0 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue