Skip to content

parser: bound recursion depth to prevent stack exhaustion on deeply nested input - #756

Open
ChrisJr404 wants to merge 1 commit into
graphql-go:masterfrom
ChrisJr404:fix-parser-recursion-depth
Open

parser: bound recursion depth to prevent stack exhaustion on deeply nested input#756
ChrisJr404 wants to merge 1 commit into
graphql-go:masterfrom
ChrisJr404:fix-parser-recursion-depth

Conversation

@ChrisJr404

@ChrisJr404 ChrisJr404 commented Aug 18, 2026

Copy link
Copy Markdown

Summary

The recursive-descent parser doesn't limit how deeply it will recurse, so a single small document made of deeply nested constructs can exhaust the goroutine stack and crash the process. Since parser.Parse runs on untrusted, client-supplied documents, this is reachable by anyone who can send a query.

The affected constructs all recurse without any depth bound:

  • nested list values / object values — parseValueLiteralparseList/parseObjectparseValueLiteral
  • nested selection sets — parseSelectionSetparseSelectionparseFieldparseSelectionSet
  • nested list types — parseTypeparseType

Reproduce

in := "{f(a:" + strings.Repeat("[", 1500000) + ")}"
parser.Parse(parser.ParseParams{Source: in})
// fatal error: stack overflow   (unrecoverable; recover() cannot catch it)

Well before the hard crash, the growing stack also makes parse time climb super-linearly — a ~64KB nested input already takes several seconds — so even inputs that don't quite reach the crash threshold are an easy denial-of-service vector.

Fix

Track the current nesting depth on the parser and return an ordinary syntax error once it exceeds a fixed limit (500). The three recursive entry points increment the counter on entry and decrement it on exit via defer, so sibling nodes don't accumulate depth and only genuine nesting counts. The limit is far higher than any realistic GraphQL document needs, so valid input is unaffected; only pathological documents are rejected, and they now get a clean error instead of taking down the process.

Tests

Added parser_depth_test.go:

  • deeply nested list values, object values, selection sets, and list types are now rejected with a syntax error instead of crashing/hanging (this test crashes/times out without the fix)
  • a moderately nested document still parses successfully

go test ./... passes.

Summary by CodeRabbit

  • Bug Fixes
    • Added protection against excessively deeply nested input during parsing.
    • Deeply nested lists, objects, selection sets, and types now return a clear syntax error instead of risking a stack overflow.
    • Moderately nested input continues to parse successfully.

…ested input

The recursive-descent parser had no limit on nesting depth. A document with
deeply nested list values, object values, selection sets, or list types (for
example "{f(a:[[[[...") drives parseValueLiteral/parseSelectionSet/parseType
into unbounded recursion. On sufficiently nested input this exhausts the
goroutine stack and aborts the process with a fatal "stack overflow", and well
before that the repeated stack growth makes parsing time grow super-linearly.
Since Parse operates on untrusted client-supplied documents, this lets a small
request take down a server.

Track the current nesting depth on the parser and return a syntax error once it
exceeds a fixed limit that is far higher than any realistic document needs.
@coderabbitai

coderabbitai Bot commented Aug 18, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7ab54203-0c52-4ba4-bfa0-7c15ee07bfe0

📥 Commits

Reviewing files that changed from the base of the PR and between 6acef35 and 43824eb.

📒 Files selected for processing (2)
  • language/parser/parser.go
  • language/parser/parser_depth_test.go

Included review availability: Your plan includes up to 10 reviews per rolling hour; 9 remain after this review.


📝 Walkthrough

Walkthrough

The parser now limits nesting to 500 levels across selection sets, value literals, and type definitions. Deep nesting returns a syntax error. Tests cover excessive nesting and successful parsing below the limit.

Changes

Parser depth limit

Layer / File(s) Summary
Depth tracking and parser enforcement
language/parser/parser.go
The parser tracks nesting depth with a 500-level limit. Selection sets, value literals, and nested types enter and leave the depth counter. Excessive nesting returns a syntax error containing the maximum-depth condition.
Deep nesting validation
language/parser/parser_depth_test.go
Tests generate deeply nested values, selections, and types and verify the depth-limit error. A separate test confirms that 100 nested list levels still parse successfully.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 43824

The parser now rejects excessively nested documents instead of risking stack exhaustion, with focused tests covering the affected constructs; no actionable merge-blocking risk remains after normal checks and review.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: limiting parser recursion depth to prevent stack exhaustion.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 95.813% (-0.001%) from 95.814% — ChrisJr404:fix-parser-recursion-depth into graphql-go:master

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.

2 participants