Skip to content

Reject JSON objects with the key 0 instead of parsing them as a batch - #27

Merged
spawnia merged 5 commits into
laragraph:masterfrom
pawell67:fix/object-with-numeric-key-is-not-a-batch
Oct 12, 2026
Merged

spawnia merged 5 commits into
laragraph:masterfrom
pawell67:fix/object-with-numeric-key-is-not-a-batch

Conversation

@pawell67

Copy link
Copy Markdown
Contributor

Problem

RequestParser::bodyParams() accepts any associative array as a single operation. A JSON object that also carries the key "0", e.g.

{"query":"{ foo }","0":"not an operation"}

decodes to a PHP array with the integer key 0. Arr::isAssoc() still reports it as associative, so the parser returns it as a single operation, but GraphQL\Server\Helper::parseRequestParams() uses isset($bodyParams[0]) to detect a batch and then calls OperationParams::create() with each entry. The string entries make that an uncaught TypeError:

GraphQL\Server\OperationParams::create(): Argument #1 ($params) must be of type array, string given, called in .../webonyx/graphql-php/src/Server/Helper.php on line 127

That surfaces as an HTTP 500 and an unhandled exception in error tracking, instead of the BadRequestGraphQLException (HTTP 400) the parser throws for every other malformed body. We hit this in production from a scanner sending a multipart-upload-shaped payload (operations, map, "0") as application/json.

Fix

Use the same rule as webonyx to tell a batch from a single operation: a body with the key 0 is a batch, and every entry of a batch must be an object. Anything else falls through to the existing BadRequestGraphQLException.

Behaviour for valid bodies is unchanged: a JSON object without the key 0 is still a single operation, a list of objects is still a batch.

Tests

  • testObjectWithNumericKeyIsNotABatch reproduces the TypeError on master and passes with the fix.
  • testBatchWithNonObjectEntry pins the existing rejection of a list containing a non-object entry.

🤖 Generated with Claude Code

RequestParser::bodyParams() accepted any associative array as a single
operation. A JSON object such as {"query":"...","0":"..."} decodes to a
PHP array with the integer key 0, which Arr::isAssoc() still reports as
associative, but which GraphQL\Server\Helper::parseRequestParams() treats
as a batch. webonyx then calls OperationParams::create() with the string
entries and the request ends in an uncaught TypeError (HTTP 500) instead
of a BadRequestGraphQLException (HTTP 400).

Use the same rule as webonyx to tell a batch from a single operation: a
body with the key 0 is a batch, and every entry of a batch must be an
object.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

A key 0 with a null value still bypasses the intended rejection path.

1 open finding
What changed in this PR

Ensures malformed GraphQL JSON bodies with numeric key 0 return HTTP 400 instead of causing an uncaught TypeError.

Changes:

  • Aligns request validation with webonyx batch detection.
  • Adds regression tests for malformed batch-like bodies.
  • Documents the fix.
File Description
src/​RequestParser.php Validates bodies detected as batches.
tests/​Unit/​RequestParserTest.php Adds malformed-body regression tests.
CHANGELOG.md Records the corrected behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/RequestParser.php Outdated
isset() is false for a null value, so [null] skipped the batch entry check.
inlineFiles() skipped the batch shape check, so multipart operations
such as {"query":"…","0":"x"} still caused a TypeError in webonyx.
@spawnia

spawnia commented Oct 12, 2026

Copy link
Copy Markdown
Member

🤖 Thanks @pawell67, I pushed three commits on top of yours.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟡 Changes recommended

The implementation is sound, but its inline and changelog documentation inaccurately overstate how null and valid object entries at key 0 are handled.

2 open findings
1 resolved since last review

🧠 Review effort: Balanced

Comment thread CHANGELOG.md Outdated
Comment thread src/RequestParser.php Outdated
@spawnia
spawnia merged commit ff6938b into laragraph:master Oct 12, 2026
42 checks passed
spawnia added a commit to webonyx/graphql-php that referenced this pull request Oct 12, 2026
Malformed batch bodies such as `["x"]` caused an uncaught `TypeError`
instead of a `RequestError`.
`Helper::parseRequestParams()` now rejects scalar batch entries.

<details>
<summary>An object with a non-null key `0` still counts as a batch, so
the message names the offending key</summary>

`{"query":"{ a }","0":"x"}` fails with `Expected batch entry query to be
an object, got: "{ a }"`.
Batch detection stays `isset($bodyParams[0])`, so `{"0":null,"query":"{
a }"}` is still a single operation.
No other server checked has a dedicated message for this case: JS
servers detect batches with `Array.isArray`, and
laragraph/utils#27 also uses a generic message.
</details>

<details>
<summary>The fix targets master only, not 14.x</summary>

14.x has the same bug, but its last release was
https://github.com/webonyx/graphql-php/releases/tag/v14.11.10 in 2023.
</details>

🤖 Generated with Claude Code
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants