Repository navigation
parser: bound nesting depth to prevent stack-overflow DoS - #516
Merged
StevenACoffman merged 2 commits intoOct 22, 2026
Merged
StevenACoffman merged 2 commits into
StevenACoffman merged 2 commits into
Conversation
The first commit guarded only selection sets; list and object values and list types still recursed without limit and crashed ParseQuery with about 2 MB of input. Count every recursive production against a single MaxNestingDepth of 1000 with no opt-out, report the error with a location, and drop the new ParseQueryWithLimits / SetMaxDepthLimit API. Also remove the gqlparser-depth-limit.patch file committed by mistake.
StevenACoffman
force-pushed
the
fix/parser-selection-set-depth-limit
branch
from
October 22, 2026 01:30
9dd2119 to
44fe9ad
Compare
Contributor
Author
|
Confirmed your 44fe9ad on my end: I reproduced all four vectors (selection sets, list values, object values, list types) at ~2M deep against your branch and they all return “exceeded maximum nesting depth” now, no crash, with normal queries unaffected. The single shared counter is cleaner than what I had, and good catch on the list-type path. Looks complete to me. Whenever you cut a release and open the GitHub Security Advisory, happy to help with the fields for the CVE. |
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: Unlimited parser token limit also have unlimited recursion depth stack overflow
Note: Users have already been recommended to set a parser token limit if there is any risk of malicious input, since CVE-2023-49559 introduced the ability to do so. Anyone who sets gqlparser's parser token limit to 15000 or less should be unaffected by this new issue, gqlgen's
srv.SetParserTokenLimit(15000). However, the gqlparser default token limit of0signifies unlimited, which is only suitable for local development or internal projects.The parser recurses once per nesting level and places no limit on depth. Deeply nested input exhausts the goroutine stack, causing
fatal error: stack overflow. That is a runtime throw, not a panic, sorecover()cannot catch it and the whole process exits.Every recursive construct is affected, not only selection sets. With default
ParseQuery(go1.27, 64-bit, 1 GB stack):{a:{a:…}}[[[…]]](arguments, directive arguments, variable defaults){a{a{…}}}[[[T]]](variable types)ParseSchemahas the same problem with list defaults and list field types.The root cause is the same as CVE-2023-49559: the parser's limits default to unlimited. A token limit does mitigate this, since the smallest crash needs about 960k tokens. But
ParseQuerysets no token limit, and neither does gqlgen by default, so one unauthenticated request crashes the server before validation runs.This was reported privately and is being coordinated for a CVE through GitHub.
Workaround for users who can't upgrade: set a parser token limit, e.g. gqlgen's
srv.SetParserTokenLimit(15000). That rejects every payload above.Change
const MaxNestingDepth = 1000. Selection sets, list and object values, and list types are counted together against it, and deeper input returns a located parse error (exceeded maximum nesting depth of 1000).parseOptionalSelectionSet(whichparseRequiredSelectionSetnow delegates to),parseList,parseObject, and the list branch ofparseTypeReference.ParseQuery,ParseQueryWithTokenLimit,ParseSchema*, and the rootLoadQuery. There's no opt-out and no new public API beyond the constant.Behaviour change
Documents nested deeper than 1000 levels, counting all kinds together, are now rejected. Real documents nest a few dozen levels, and a 1000-deep query parses and validates in about 7 ms.
Tests
parser/nesting_test.go: