Skip to content

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

Merged
chr-hertel merged 2 commits into
modelcontextprotocol:mainfrom
soyuka:fix/jsonrpc-input-hardening
Jul 5, 2026
Merged

[Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion#383
chr-hertel merged 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
@chr-hertel
chr-hertel merged commit df5379c into modelcontextprotocol:main Jul 5, 2026
36 checks passed
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