Skip to content

Commit d3a1841

Browse files
authored
Merge commit from fork
* 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
1 parent fa851d9 commit d3a1841

File tree

2 files changed

+55
-0
lines changed

2 files changed

+55
-0
lines changed

src/mcp/server/fastmcp/server.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,14 @@ def __init__( # noqa: PLR0913
174174
auth: AuthSettings | None = None,
175175
transport_security: TransportSecuritySettings | None = None,
176176
):
177+
# Auto-enable DNS rebinding protection for localhost (IPv4 and IPv6)
178+
if transport_security is None and host in ("127.0.0.1", "localhost", "::1"):
179+
transport_security = TransportSecuritySettings(
180+
enable_dns_rebinding_protection=True,
181+
allowed_hosts=["127.0.0.1:*", "localhost:*", "[::1]:*"],
182+
allowed_origins=["http://127.0.0.1:*", "http://localhost:*", "http://[::1]:*"],
183+
)
184+
177185
self.settings = Settings(
178186
debug=debug,
179187
log_level=log_level,

tests/server/fastmcp/test_server.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from mcp.server.fastmcp.resources import FileResource, FunctionResource
1313
from mcp.server.fastmcp.utilities.types import Audio, Image
1414
from mcp.server.session import ServerSession
15+
from mcp.server.transport_security import TransportSecuritySettings
1516
from mcp.shared.exceptions import McpError
1617
from mcp.shared.memory import (
1718
create_connected_server_and_client_session as client_session,
@@ -183,6 +184,52 @@ def get_data(x: str) -> str: # pragma: no cover
183184
return f"Data: {x}"
184185

185186

187+
class TestDnsRebindingProtection:
188+
"""Tests for automatic DNS rebinding protection on localhost."""
189+
190+
def test_auto_enabled_for_127_0_0_1(self):
191+
"""DNS rebinding protection should auto-enable for host=127.0.0.1."""
192+
mcp = FastMCP(host="127.0.0.1")
193+
assert mcp.settings.transport_security is not None
194+
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
195+
assert "127.0.0.1:*" in mcp.settings.transport_security.allowed_hosts
196+
assert "localhost:*" in mcp.settings.transport_security.allowed_hosts
197+
assert "http://127.0.0.1:*" in mcp.settings.transport_security.allowed_origins
198+
assert "http://localhost:*" in mcp.settings.transport_security.allowed_origins
199+
200+
def test_auto_enabled_for_localhost(self):
201+
"""DNS rebinding protection should auto-enable for host=localhost."""
202+
mcp = FastMCP(host="localhost")
203+
assert mcp.settings.transport_security is not None
204+
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
205+
assert "127.0.0.1:*" in mcp.settings.transport_security.allowed_hosts
206+
assert "localhost:*" in mcp.settings.transport_security.allowed_hosts
207+
208+
def test_auto_enabled_for_ipv6_localhost(self):
209+
"""DNS rebinding protection should auto-enable for host=::1 (IPv6 localhost)."""
210+
mcp = FastMCP(host="::1")
211+
assert mcp.settings.transport_security is not None
212+
assert mcp.settings.transport_security.enable_dns_rebinding_protection is True
213+
assert "[::1]:*" in mcp.settings.transport_security.allowed_hosts
214+
assert "http://[::1]:*" in mcp.settings.transport_security.allowed_origins
215+
216+
def test_not_auto_enabled_for_other_hosts(self):
217+
"""DNS rebinding protection should NOT auto-enable for other hosts."""
218+
mcp = FastMCP(host="0.0.0.0")
219+
assert mcp.settings.transport_security is None
220+
221+
def test_explicit_settings_not_overridden(self):
222+
"""Explicit transport_security settings should not be overridden."""
223+
custom_settings = TransportSecuritySettings(
224+
enable_dns_rebinding_protection=False,
225+
)
226+
mcp = FastMCP(host="127.0.0.1", transport_security=custom_settings)
227+
# Settings are copied by pydantic, so check values not identity
228+
assert mcp.settings.transport_security is not None
229+
assert mcp.settings.transport_security.enable_dns_rebinding_protection is False
230+
assert mcp.settings.transport_security.allowed_hosts == []
231+
232+
186233
def tool_fn(x: int, y: int) -> int:
187234
return x + y
188235

0 commit comments

Comments
 (0)