fix: disable MCP DNS-rebinding host guard behind reverse proxy
The MCP SDK's transport-security DNS-rebinding protection only accepts a localhost Host header by default and returns 421 behind Traefik (Host = fleetmcp.*). It targets browser attacks on localhost-bound servers and does not apply to a public, TLS-terminated, Bearer-authenticated service. Off by default now; re-enableable via MCP_DNS_REBINDING_PROTECTION=1 + MCP_ALLOWED_HOSTS. Also: deploy.sh health echo uses python (slim image has no curl). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
6d79ad32fb
commit
0c4848c656
3 changed files with 31 additions and 2 deletions
|
|
@ -69,6 +69,14 @@ Then **connect the app to the network that can reach `timescale_db`** (the track
|
|||
stack's network) so the `timescale_db` hostname resolves. Coolify manages the Traefik
|
||||
labels + TLS from the domain you set.
|
||||
|
||||
Optional env (sensible defaults):
|
||||
- `MCP_MAX_ROWS` (default `10000`) — hard ceiling on rows a query may return.
|
||||
- `MCP_DNS_REBINDING_PROTECTION` (default `0`/off) — the MCP SDK's localhost Host-header
|
||||
guard. It returns `421` behind a reverse proxy, so it's off by default (the service is
|
||||
TLS-terminated + Bearer-authed). Set `1` to enforce, with `MCP_ALLOWED_HOSTS`.
|
||||
- `MCP_ALLOWED_HOSTS` — comma-separated allowlist used only when the guard is on
|
||||
(default: the two `fleetmcp.*` domains + localhost).
|
||||
|
||||
**1b. Manual host deploy (fallback):** check this repo out on the host and run `deploy.sh`
|
||||
— it builds the image, derives the read-only `DATABASE_URL` from the running stack, and
|
||||
runs a standalone Traefik bridge. See the script header.
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import psycopg2
|
|||
import psycopg2.extras
|
||||
import psycopg2.pool
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
from starlette.middleware.base import BaseHTTPMiddleware
|
||||
from starlette.responses import JSONResponse
|
||||
|
||||
|
|
@ -143,7 +144,27 @@ def _guard(sql: str) -> str:
|
|||
|
||||
|
||||
# ── MCP server + tools ───────────────────────────────────────────────────────
|
||||
mcp = FastMCP("fireside-analytics", stateless_http=True)
|
||||
# The MCP SDK ships DNS-rebinding protection that, by default, only accepts a
|
||||
# localhost Host header and returns 421 for anything else — which breaks this
|
||||
# service behind Traefik (Host = fleetmcp.*). That protection targets browser
|
||||
# attacks on localhost-bound servers; it does not apply to a public, TLS-terminated,
|
||||
# Bearer-authenticated service. So it is OFF by default here, and re-enableable via
|
||||
# MCP_DNS_REBINDING_PROTECTION=1 with an explicit MCP_ALLOWED_HOSTS allowlist.
|
||||
_DNS_PROT = os.getenv("MCP_DNS_REBINDING_PROTECTION", "0") == "1"
|
||||
_ALLOWED_HOSTS = [
|
||||
h.strip()
|
||||
for h in os.getenv(
|
||||
"MCP_ALLOWED_HOSTS",
|
||||
"fleetmcp.fivetitude.com,fleetmcp.rahamafresh.com,localhost,127.0.0.1",
|
||||
).split(",")
|
||||
if h.strip()
|
||||
]
|
||||
_transport_security = TransportSecuritySettings(
|
||||
enable_dns_rebinding_protection=_DNS_PROT,
|
||||
allowed_hosts=_ALLOWED_HOSTS,
|
||||
allowed_origins=[f"https://{h}" for h in _ALLOWED_HOSTS],
|
||||
)
|
||||
mcp = FastMCP("fireside-analytics", stateless_http=True, transport_security=_transport_security)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
|
|
|||
|
|
@ -74,4 +74,4 @@ rm -f "$ENV_FILE"
|
|||
sleep 5
|
||||
echo "== container =="; docker ps --filter name="$NAME" --format "{{.Names}} | {{.Status}}"
|
||||
echo "== DB role (expect analytics_ro) =="; docker exec "$NAME" sh -lc 'printenv DATABASE_URL | sed -E "s#://([^:]+):[^@]+@#://\1:<pw>@#"'
|
||||
echo "== health =="; docker exec "$NAME" sh -lc "curl -s http://localhost:${PORT}/healthz" 2>&1 | head
|
||||
echo "== health =="; docker exec "$NAME" python -c "import urllib.request; print(urllib.request.urlopen('http://localhost:${PORT}/healthz').read().decode())" 2>&1 | head
|
||||
|
|
|
|||
Loading…
Reference in a new issue