Skip to content

fix: reject undefined input object fields supplied via variables - #765

Open
derekadams wants to merge 1 commit into
graph-gophers:mainfrom
devicechain-io:fix/reject-unknown-input-object-fields-in-variables
Open

fix: reject undefined input object fields supplied via variables#765
derekadams wants to merge 1 commit into
graph-gophers:mainfrom
devicechain-io:fix/reject-unknown-input-object-fields-in-variables

Conversation

@derekadams

Copy link
Copy Markdown

Problem

An input object value supplied through a variable is accepted even when it contains entries the input object type does not define. The undefined entries are silently discarded and the request succeeds. The same value written as a literal in the query is correctly rejected.

input SearchFilter { required: String!  optional: String }
# rejected today (correct)
query { search(filter: {required: "a", undefined: "b"}) { match } }
# => Field "undefined" is not defined by type "SearchFilter".

# accepted today (should be an error)
query q($filter: SearchFilter!) { search(filter: $filter) { match } }
# variables: {"filter": {"required": "a", "undefined": "b"}}
# => {"data": {...}}

This is easy to hit in practice: a client that misnames a field gets a success response and a partially-applied input, with nothing to indicate a value was dropped.

Cause

validateValue's *ast.InputObject case iterates the type's declared fields and looks each one up in the supplied map:

for _, f := range t.Values {
    fieldVal := in[f.Name.Name]
    validateValue(c, f, fieldVal, f.Type)
}

An entry present in the map but absent from the type is never visited. validateValueType (the literal path) iterates the other direction — provided fields, looked up in the type — and already reports Field "x" is not defined by type "Y", which is why literals are caught.

Spec

Section 3.10, Input Coercion, requires the error, and names the variable case explicitly:

The value for an input object should be an input object literal or an unordered map supplied by a variable, otherwise a request error must be raised. In either case, the input object literal or unordered map must not contain any entries with names not defined by a field of this input object type, otherwise a request error must be raised.

Section 6.1.2 routes variable values through those same coercion rules. graphql-js rejects this case in coerceInputValue.

Change

Walk the supplied map as well, reporting entries not defined by the type, and reuse the existing makeSuggestion helper so the message matches the literal path. Undefined entries are collected and sorted before being reported, so Go's randomized map iteration order does not make the error output vary between runs.

Two cases added to TestQueryVariablesValidation, covering the rejection and the did-you-mean suggestion. The full test suite passes (go test -count=1 ./...).

Relationship to #292

#292 ("Query variables are not validated") fixed the missing-required-key direction of this same literal-vs-variable asymmetry. This is the unknown-key counterpart, which that change did not cover.

It is also structurally invisible to the graphql-js parity work in #708: graphql-js catches this at execution-time coercion rather than in a validation rule, so importing the validation rules could not surface it.

Note, not addressed here

The @oneOf exactly-one-key constraint appears to have the same asymmetry — enforced for literals, not for values arriving via variables. I left it out to keep this diff focused, but I'm happy to follow up if you'd like it in the same PR.

🤖 Generated with Claude Code

An input object value supplied through a variable was accepted even when it
contained entries the input object type does not define; the undefined entries
were silently discarded and the request succeeded.

validateValue's *ast.InputObject case iterated the type's declared fields and
looked each one up in the supplied map, so an entry present in the map but absent
from the type was never visited. The literal path (validateValueType) iterates the
other direction -- provided fields, looked up in the type -- and already reports
`Field "x" is not defined by type "Y"`. This aligns the variable path with it.

The spec requires this: section 3.10 Input Coercion states that an input object
literal "or unordered map supplied by a variable ... must not contain any entries
with names not defined by a field of this input object type, otherwise a request
error must be raised". graphql-js rejects this case in coerceInputValue.

Undefined entries are collected and sorted before being reported so that the
errors do not vary between runs with Go's randomized map iteration order.

This is the unknown-key counterpart to the missing-key direction fixed in graph-gophers#292.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

1 participant