Skip to content

[Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion#383

Open
soyuka wants to merge 2 commits into
modelcontextprotocol:mainfrom
soyuka:fix/jsonrpc-input-hardening
Open

[Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion#383
soyuka wants to merge 2 commits into
modelcontextprotocol:mainfrom
soyuka:fix/jsonrpc-input-hardening

Conversation

@soyuka

@soyuka soyuka commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Problem

MessageFactory::create() decided single-vs-batch by inspecting the raw first byte ($input[0] === '{'). That misclassifies valid-but-non-object JSON:

  • a scalar payload (5, "x", true) falls through and the subsequent foreach runs over a non-array — a PHP warning and a silently-empty result (or a TypeError on a batch element);
  • a leading-whitespace object (" {...}") is treated as a batch and the loop iterates the object's values as if they were messages.

There was also no bound on batch size, so a single small request could expand into arbitrarily many operations (amplification), and no bound on the HTTP request body size.

Changes

  • Type-based message detection. Determine single message vs batch from the decoded JSON type (object → single, list array → batch) instead of the first byte. Scalars and empty payloads are returned as InvalidInputMessageException entries — consistent with the existing per-message error contract — rather than crashing or no-oping.
  • Batch size cap. New maxBatchSize (default 100); oversized batches are rejected before any message is constructed.
  • Non-object batch elements are reported as InvalidInputMessageException instead of reaching a TypeError.
  • Bounded HTTP body. StreamableHttpTransport gains maxBodyBytes (default 4 MiB) and returns 413 for oversized bodies. Unknown-size/chunked bodies are read incrementally and stopped at the cap so they cannot exhaust memory.

Both limits are constructor-configurable. MessageFactory::create()'s contract is unchanged: it still throws only \JsonException for unparseable JSON; every other invalidity is surfaced as an InvalidInputMessageException in the returned array, so callers need no changes.

Tests

  • MessageFactoryTest: scalar/string rejected, empty batch rejected, batch element must be object, leading-whitespace object parsed as a single message, batch size over/within cap, non-positive maxBatchSize throws.
  • StreamableHttpTransportTest: body over cap → 413, body within cap not rejected, non-positive maxBodyBytes throws.

Full suite green (874 tests), PHPStan and PHP-CS-Fixer clean. Backward compatible — new constructor args are optional.

MessageFactory::create() detected batches via $input[0] === '{', which
mishandled valid-but-non-object JSON: a scalar ("5") or leading-whitespace
object silently returned [] (with a PHP warning) or could reach a foreach
over a non-array. It also had no bound on batch size, letting one small
request expand into arbitrarily many operations (amplification).

- Detect single-vs-batch from the decoded JSON type (object vs list array)
  instead of the raw first byte; reject scalars and empty payloads as
  InvalidInputMessageException rather than crashing or no-oping.
- Cap batch size (maxBatchSize, default 100) and reject oversized batches.
- Reject non-object batch elements as InvalidInputMessageException instead
  of letting them hit a TypeError.
- Bound the StreamableHttpTransport POST body (maxBodyBytes, default 4 MiB),
  returning 413, with an incremental read so unknown-size/chunked bodies
  cannot exhaust memory.
chr-hertel
chr-hertel previously approved these changes Jun 15, 2026

@chr-hertel chr-hertel left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docs and CHANGELOG would be nice, other then that LGTM, thanks! 👍

Add CHANGELOG entries and docs/transports.md sections for the JSON-RPC
batch size cap (maxBatchSize) and the StreamableHttpTransport request
body size cap (maxBodyBytes), plus the type-based single-vs-batch
message detection.
@chr-hertel chr-hertel changed the title Harden JSON-RPC input parsing against malformed input and resource exhaustion [Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion Jun 15, 2026
@chr-hertel chr-hertel added the Server Issues & PRs related to the Server component label Jun 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Server Issues & PRs related to the Server component

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants