fix: harden MCP endpoint after code review
Critical: the MCP SDK auto-enables DNS-rebinding protection when its host parameter is left at the 127.0.0.1 default, hard-coding a loopback-only Host allow-list. Every remote client (the only deployment this feature targets) was refused with HTTP 421 before auth or the tool ran. Now driven by a new MCP_ALLOWED_HOSTS setting, with '*' as an explicit, logged opt-out. Also bounds query/top_k to match AskRequest (top_k is amplified 4x downstream, so an unbounded value was a resource-exhaustion vector), decodes the Authorization header as latin-1 per the ASGI spec instead of raising a 500 on malformed bytes, and returns WWW-Authenticate on 401 per RFC 7235. Moves the psycopg2 import guard into backend/tests/conftest.py: duplicated across four test modules, it only worked because of alphabetical collection order, and any earlier-sorting package would have reintroduced a live connection attempt against the production database. Registers the mcp module in the authoritative backend architecture doc. 84 backend tests pass. Verified against a live server: allowed remote Host returns a valid initialize result, unknown Host returns 421, missing token returns 401 with WWW-Authenticate. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -198,6 +198,20 @@ class Settings(BaseSettings):
|
||||
description="Comma-separated allowed CORS origins. Never use * in production.",
|
||||
)
|
||||
|
||||
# ── MCP ───────────────────────────────────────────────────────────────────
|
||||
# The MCP SDK enables DNS-rebinding protection whenever the transport is
|
||||
# bound to a loopback host, which rejects any Host header not in this list
|
||||
# with HTTP 421. Deployments reachable by a real hostname/IP must list it
|
||||
# here or every remote MCP client is refused before the handler runs.
|
||||
mcp_allowed_hosts: str = Field(
|
||||
default="127.0.0.1:*,localhost:*,[::1]:*",
|
||||
description=(
|
||||
"Comma-separated Host header values accepted by the MCP endpoint. "
|
||||
"A ':*' suffix matches any port. Set to '*' to disable DNS-rebinding "
|
||||
"protection entirely (not recommended)."
|
||||
),
|
||||
)
|
||||
|
||||
@lru_cache
|
||||
def get_settings() -> Settings:
|
||||
"""Return settings."""
|
||||
|
||||
Reference in New Issue
Block a user