From 13660b4398894c2f91eddb4109d60470280e18dd Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Thu, 24 Sep 2026 18:46:52 -0400 Subject: [PATCH] Fix duplicated Host header rejecting all MCP requests under PHP-FPM (#111) nyholm's ServerRequestCreator adds Host both from the URI it builds and from the request headers, so under PHP-FPM getHeaderLine('Host') returns "beta.t.lotusguild.org, beta.t.lotusguild.org". The SDK's DNS-rebinding check compares that joined string against the allowlist and refused every request with 403 "Invalid Host header", even for the correct hostname. It only passed locally because PHP's built-in server exposes headers differently. Collapse Host to the single value the client sent before the middleware runs. The rebinding check still sees the client's real Host, so foreign hosts and direct-by-IP access stay refused. Verified on the beta host by running the patched entrypoint against beta's real config and vendor/: the correct host gets the Protected Resource Metadata, and a foreign host still gets 403. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X --- mcp/server.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/mcp/server.php b/mcp/server.php index bc97d74..e9e2dca 100644 --- a/mcp/server.php +++ b/mcp/server.php @@ -48,6 +48,15 @@ if (empty($resourceUrl) || empty($issuer)) { $psr17 = new Psr17Factory(); $request = (new ServerRequestCreator($psr17, $psr17, $psr17, $psr17))->fromGlobals(); +// ServerRequestCreator adds Host both from the URI and from the request +// headers, so getHeaderLine('Host') comes back as "h, h" under PHP-FPM, which +// the DNS-rebinding check below then rejects. Collapse to the single value the +// client actually sent. +$clientHost = $request->getHeader('Host')[0] ?? ''; +if ($clientHost !== '') { + $request = $request->withHeader('Host', $clientHost); +} + // TLS terminates at the reverse proxy, so PHP sees plain http and a Host header // the client controls. The SDK derives the resource_metadata URL in its 401 // challenge from the request URI, so pin scheme/host/port to the configured