[Server] Reject malformed MCP session ID headers (#384)
* [Server] Reject malformed MCP session ID headers * [Server] Narrow invalid session ID exception handling * [Server] Keep UUID catch compatible with supported Symfony
This commit is contained in:
parent
8e18bfbdf1
commit
d187002d87
2 changed files with 46 additions and 2 deletions
|
|
@ -343,8 +343,19 @@ class StreamableHttpTransport extends BaseTransport
|
||||||
private function handleRequest(ServerRequestInterface $request): ResponseInterface
|
private function handleRequest(ServerRequestInterface $request): ResponseInterface
|
||||||
{
|
{
|
||||||
$this->request = $request;
|
$this->request = $request;
|
||||||
$sessionIdString = $request->getHeaderLine(self::SESSION_HEADER);
|
$sessionIdHeaders = $request->getHeader(self::SESSION_HEADER);
|
||||||
$this->sessionId = $sessionIdString ? Uuid::fromString($sessionIdString) : null;
|
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;
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
return match ($request->getMethod()) {
|
return match ($request->getMethod()) {
|
||||||
'OPTIONS' => $this->handleOptionsRequest(),
|
'OPTIONS' => $this->handleOptionsRequest(),
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,39 @@ final class StreamableHttpTransportTest extends TestCase
|
||||||
$this->assertSame(400, $response->getStatusCode());
|
$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')]
|
#[TestDox('explicit empty middleware list disables defaults and emits a warning log')]
|
||||||
public function testEmptyMiddlewareListDisablesDefaultsAndWarns(): void
|
public function testEmptyMiddlewareListDisablesDefaultsAndWarns(): void
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue