Fix duplicated Host header rejecting all MCP requests under PHP-FPM (#111)
Lint / PHP (phpcs PSR-12) (push) Successful in 40s
Lint / JS (eslint) (push) Successful in 12s
Lint / PHP requirements (version + extensions) (push) Successful in 21s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m8s
Lint / Deploy (push) Successful in 2s
Lint / PHP (phpcs PSR-12) (push) Successful in 40s
Lint / JS (eslint) (push) Successful in 12s
Lint / PHP requirements (version + extensions) (push) Successful in 21s
Lint / Notify on failure (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m8s
Lint / Deploy (push) Successful in 2s
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MGDKHiU5RJdo3dqQUDow3X
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user