From 51e91dabbc3df516e00118985bdf71a0be451d6d Mon Sep 17 00:00:00 2001 From: King Star Date: Fri, 19 Jun 2026 19:31:56 +0800 Subject: [PATCH 1/3] [Server] Reject malformed MCP session ID headers --- .../Transport/StreamableHttpTransport.php | 14 ++++++-- .../Transport/StreamableHttpTransportTest.php | 33 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/src/Server/Transport/StreamableHttpTransport.php b/src/Server/Transport/StreamableHttpTransport.php index ab84b092..7b475cf3 100644 --- a/src/Server/Transport/StreamableHttpTransport.php +++ b/src/Server/Transport/StreamableHttpTransport.php @@ -294,8 +294,18 @@ private static function normalizeMiddleware(iterable $middleware): array private function handleRequest(ServerRequestInterface $request): ResponseInterface { $this->request = $request; - $sessionIdString = $request->getHeaderLine(self::SESSION_HEADER); - $this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null; + $sessionIdHeaders = $request->getHeader(self::SESSION_HEADER); + if (\count($sessionIdHeaders) > 1) { + return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must not be repeated.'), 400); + } + + $sessionIdString = $sessionIdHeaders[0] ?? ''; + + try { + $this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null; + } catch (\InvalidArgumentException) { + return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must be a valid UUID.'), 400); + } return match ($request->getMethod()) { 'OPTIONS' => $this->handleOptionsRequest(), diff --git a/tests/Unit/Server/Transport/StreamableHttpTransportTest.php b/tests/Unit/Server/Transport/StreamableHttpTransportTest.php index 0f949604..b5c93617 100644 --- a/tests/Unit/Server/Transport/StreamableHttpTransportTest.php +++ b/tests/Unit/Server/Transport/StreamableHttpTransportTest.php @@ -85,6 +85,39 @@ public function testDefaultMiddlewareRejectsUnsupportedProtocolVersion(): void $this->assertSame(400, $response->getStatusCode()); } + #[TestDox('malformed MCP session IDs are rejected as bad requests')] + public function testMalformedSessionIdHeaderReturnsBadRequest(): void + { + $request = $this->factory + ->createServerRequest('POST', 'http://localhost/') + ->withHeader('Host', 'localhost') + ->withHeader(StreamableHttpTransport::SESSION_HEADER, '{"not":"a-token"}'); + + $transport = new StreamableHttpTransport($request, $this->factory, $this->factory); + + $response = $transport->listen(); + + $this->assertSame(400, $response->getStatusCode()); + $this->assertStringContainsString(StreamableHttpTransport::SESSION_HEADER, (string) $response->getBody()); + } + + #[TestDox('duplicate MCP session ID headers are rejected as bad requests')] + public function testDuplicateSessionIdHeadersReturnBadRequest(): void + { + $request = $this->factory + ->createServerRequest('POST', 'http://localhost/') + ->withHeader('Host', 'localhost') + ->withHeader(StreamableHttpTransport::SESSION_HEADER, '2fb587fc-593f-47ce-9d9a-9c06f2b907a3') + ->withAddedHeader(StreamableHttpTransport::SESSION_HEADER, '5e583da8-a677-4446-b723-4ddbe00fda62'); + + $transport = new StreamableHttpTransport($request, $this->factory, $this->factory); + + $response = $transport->listen(); + + $this->assertSame(400, $response->getStatusCode()); + $this->assertStringContainsString('must not be repeated', (string) $response->getBody()); + } + #[TestDox('explicit empty middleware list disables defaults and emits a warning log')] public function testEmptyMiddlewareListDisablesDefaultsAndWarns(): void { From d07b7ba04afe9c3ae51130833ec4937f77e33331 Mon Sep 17 00:00:00 2001 From: King Star Date: Sat, 11 Jul 2026 18:12:14 +0800 Subject: [PATCH 2/3] [Server] Narrow invalid session ID exception handling --- src/Server/Transport/StreamableHttpTransport.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Server/Transport/StreamableHttpTransport.php b/src/Server/Transport/StreamableHttpTransport.php index 7b475cf3..e31597b4 100644 --- a/src/Server/Transport/StreamableHttpTransport.php +++ b/src/Server/Transport/StreamableHttpTransport.php @@ -24,6 +24,7 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Log\LoggerInterface; +use Symfony\Component\Uid\Exception\InvalidArgumentException as UidInvalidArgumentException; use Symfony\Component\Uid\Uuid; /** @@ -303,7 +304,7 @@ private function handleRequest(ServerRequestInterface $request): ResponseInterfa try { $this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null; - } catch (\InvalidArgumentException) { + } catch (UidInvalidArgumentException) { return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must be a valid UUID.'), 400); } From bc1d3bc782a704409ae6be622771abaa6667574d Mon Sep 17 00:00:00 2001 From: King Star Date: Sat, 11 Jul 2026 18:44:22 +0800 Subject: [PATCH 3/3] [Server] Keep UUID catch compatible with supported Symfony --- src/Server/Transport/StreamableHttpTransport.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Server/Transport/StreamableHttpTransport.php b/src/Server/Transport/StreamableHttpTransport.php index e31597b4..650fbacf 100644 --- a/src/Server/Transport/StreamableHttpTransport.php +++ b/src/Server/Transport/StreamableHttpTransport.php @@ -24,7 +24,6 @@ use Psr\Http\Message\StreamFactoryInterface; use Psr\Http\Server\MiddlewareInterface; use Psr\Log\LoggerInterface; -use Symfony\Component\Uid\Exception\InvalidArgumentException as UidInvalidArgumentException; use Symfony\Component\Uid\Uuid; /** @@ -304,7 +303,8 @@ private function handleRequest(ServerRequestInterface $request): ResponseInterfa try { $this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null; - } catch (UidInvalidArgumentException) { + // Symfony UID 5.4/6.4 throw the global parent; newer versions throw a namespaced subclass. + } catch (\InvalidArgumentException) { return $this->createErrorResponse(Error::forInvalidRequest(self::SESSION_HEADER.' header must be a valid UUID.'), 400); }