#754·graphql

No limit in nested selection sets

Author: hibrian827Created Jul 7, 2026Updated Jul 7, 2026

Summary

graphql-go's public parser can be made to exhaust the Go runtime stack while parsing a syntactically valid, deeply nested GraphQL selection set. The issue is in the recursive selection-set parser path under language/parser/parser.go: the parser descends through nested fields before validation or execution depth limits can run, so a single large document can terminate the hosting process. This may have some security-relevant effects but we were not able to find any means of contact for private report.

Affected

Root cause

The exported Parse entry point accepts caller-controlled input and wraps string sources into the parser's source.Source at language/parser/parser.go:56. It then immediately calls parseDocument at language/parser/parser.go:69; a document beginning with { is treated as an anonymous operation at language/parser/parser.go:143, and parseOperationDefinition hands that selection directly to parseSelectionSet at language/parser/parser.go:178. parseSelectionSet parses { Selection+ } through reverse(..., parseSelection, ...) at language/parser/parser.go:317, while reverse repeatedly calls the supplied parse function at language/parser/parser.go:1582 without tracking nesting depth. For ordinary fields, parseSelection dispatches to parseField at language/parser/parser.go:347; when the next token is another {, parseField recursively enters parseSelectionSet at language/parser/parser.go:382. A document shaped like { a { a { ... } } } therefore grows the Go call stack once per nested selection and has no parser-level guard that converts excessive depth into an error.

Reproduction

go
package main
import (
"fmt"
"strings"
"github.com/graphql-go/graphql/language/parser"
)
func main() {
query := "{" + strings.Repeat("a{", 1_000_000)
_, err := parser.Parse(parser.ParseParams{Source: query})
fmt.Println("parsed without overflow, err:", err) // never reached
}

Suggested fix

diff --git a/language/parser/parser.go b/language/parser/parser.go
index 0c75906..3acfd93 100644
--- a/language/parser/parser.go
+++ b/language/parser/parser.go
@@ -17,6 +17,8 @@ type parseFn func(parser *Parser) (interface{}, error)
// parse operation, fragment, typeSystem{schema, type..., extension, directives} definition
type parseDefinitionFn func(parser *Parser) (ast.Node, error)

+const maxSelectionSetDepth = 1000
+
var tokenDefinitionFn map[string]parseDefinitionFn

func init() {
@@ -47,11 +49,12 @@ type ParseParams struct {
}

type Parser struct {
- LexToken lexer.Lexer
- Source *source.Source
- Options ParseOptions
- PrevEnd int
- Token lexer.Token
+ LexToken lexer.Lexer
+ Source *source.Source
+ Options ParseOptions
+ PrevEnd int
+ Token lexer.Token
+ SelectionSetDepth int
}

func Parse(p ParseParams) (*ast.Document, error) {
@@ -315,6 +318,14 @@ func parseVariable(parser *Parser) (*ast.Variable, error) {
* SelectionSet : { Selection+ }
*/
func parseSelectionSet(parser *Parser) (*ast.SelectionSet, error) {
+ if parser.SelectionSetDepth >= maxSelectionSetDepth {
+ return nil, gqlerrors.NewSyntaxError(parser.Source, parser.Token.Start, "Selection set nesting is too deep.")
+ }
+ parser.SelectionSetDepth++
+ defer func() {
+ parser.SelectionSetDepth--
+ }()
+
start := parser.Token.Start
selections := []ast.Selection{}
if iSelections, err := reverse(parser,

Reported by Team Atlanta.