[Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion#383
Open
soyuka wants to merge 2 commits into
Open
[Server] Harden JSON-RPC input parsing against malformed input and resource exhaustion#383soyuka wants to merge 2 commits into
soyuka wants to merge 2 commits into
Conversation
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
previously approved these changes
Jun 15, 2026
chr-hertel
left a comment
Member
There was a problem hiding this comment.
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
approved these changes
Jun 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
MessageFactory::create()decided single-vs-batch by inspecting the raw first byte ($input[0] === '{'). That misclassifies valid-but-non-object JSON:5,"x",true) falls through and the subsequentforeachruns over a non-array — a PHP warning and a silently-empty result (or aTypeErroron a batch element);" {...}") 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
InvalidInputMessageExceptionentries — consistent with the existing per-message error contract — rather than crashing or no-oping.maxBatchSize(default 100); oversized batches are rejected before any message is constructed.InvalidInputMessageExceptioninstead of reaching aTypeError.StreamableHttpTransportgainsmaxBodyBytes(default 4 MiB) and returns413for 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\JsonExceptionfor unparseable JSON; every other invalidity is surfaced as anInvalidInputMessageExceptionin 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-positivemaxBatchSizethrows.StreamableHttpTransportTest: body over cap → 413, body within cap not rejected, non-positivemaxBodyBytesthrows.Full suite green (874 tests), PHPStan and PHP-CS-Fixer clean. Backward compatible — new constructor args are optional.