Fixed validation denial of service through fragment cycles and fragment bombs - #624
Open
xperiandri wants to merge 1 commit into
Open
xperiandri wants to merge 1 commit into
xperiandri wants to merge 1 commit into
Conversation
…nt bombs - Validation measures, before building its context, how many selections inlining the fragment spreads produces and how deep it nests, and rejects documents over `DocumentLimitsDefaults.MaxRecursiveSelections` (25 000) or `MaxNestingDepth` (128) - Validation stops after `DocumentLimitsDefaults.MaxValidationErrors` (100) errors, as graphql-js does - The subscription single root field rule collects each fragment once per operation, so a fragment spreading itself no longer overflows the stack, and counts the fields selected before a fragment spread - The fragment cycle rule finds cycles with Tarjan's algorithm in linear time, and spreads of cyclic fragments are no longer inlined by the other rules - The unused variable rule searches each fragment once - `ValidationResult.collect` accumulates errors in linear time Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
xperiandri
force-pushed
the
validation-dos-fixes
branch
from
October 3, 2026 00:21
375053a to
df09a89
Compare
Contributor
There was a problem hiding this comment.
Copilot review overview
🟡 Changes recommended
Subscription validation still counts duplicate selections with the same response key as separate root fields.
Review effort: Balanced
Findings: 1
Open (1)
What changed in this PR
Adds validation safeguards against fragment-cycle stack overflows and fragment-bomb denial-of-service attacks.
Changes:
- Adds document size, depth, and validation-error limits.
- Introduces cycle-safe fragment traversal and linear error collection.
- Adds regression tests and release notes.
| File | Description |
|---|---|
RELEASE_NOTES.md |
Documents validation behavior changes. |
DocumentLimits.fs |
Defines default validation limits. |
FSharp.Data.GraphQL.Shared.fsproj |
Includes the limits module. |
Validation.fs |
Implements limits and cycle-safe validation. |
ValidationTypes.fs |
Optimizes error accumulation. |
FSharp.Data.GraphQL.Tests.fsproj |
Includes the new tests. |
ValidationDoSTests.fs |
Covers denial-of-service regressions. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Comment on lines
+723
to
+735
| let visitedFragments = HashSet<string> (StringComparer.Ordinal) | ||
| let rec getFieldNames (names : string list) (selectionSet : Selection list) = | ||
| (names, selectionSet) | ||
| ||> List.fold (fun acc -> | ||
| function | ||
| | Field field -> field.AliasOrName :: acc | ||
| | InlineFragment frag -> getFieldNames acc frag.SelectionSet | ||
| | FragmentSpread spread when visitedFragments.Add spread.Name -> | ||
| match fragments.TryGetValue spread.Name with | ||
| | true, frag -> getFieldNames acc frag.SelectionSet | ||
| | false, _ -> acc | ||
| | FragmentSpread _ -> acc) | ||
| let fieldNames = getFieldNames [] def.SelectionSet |
Test Results 9 files 9 suites 14m 18s ⏱️ Results for commit df09a89. |
xperiandri
added this pull request to stack #627
October 3, 2026 01:04
This branch has not been deployed
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
Two documents could take any server down through validation alone:
query Q { hello } subscription S { ...F } fragment F on Query { ...F }crashed the process with a stack overflow invalidateSubscriptionSingleRootField, even withQselected and no subscription type in the schema.CreateExecutionPlan, because the validation context inlines every fragment spread per path. Fragments spreading each other in a cycle grew factorially in the same way.Changes
measureDocumentcounts, in linear time and iteratively, the selections that inlining fragment spreads produces over all operations and fragment definitions, and the nesting depth. The count of each fragment is computed once, as in Apollo Server'sRecursiveSelectionsLimit.validateDocumentrejects documents overDocumentLimitsDefaults.MaxRecursiveSelections(25 000) orMaxNestingDepth(128) before building anything else.DocumentLimitsDefaults.MaxValidationErrors(100) errors and adds "Too many validation errors, error limit reached. Validation aborted.", as graphql-js does.@defer/@streamrules.ValidationResult.collect. Accumulates errors in linear time instead of copying the accumulated list on every step.The 25 000 default keeps validating and planning a document at the limit under a second. At 100 000 selections, planning alone takes about two seconds.
Behavior changes (in the release notes)
Tests
ValidationDoSTests.fscontains 24 xUnit tests. Stack-sensitive tests run on a 1 MiB thread with a timeout. Some documents are ported from Hot Chocolate's and Apollo Server's tests (MIT, attributed).Executor.CreateExecutionPlan.The full FAKE pipeline passes locally: 788 unit tests (5 skipped, as before) and 108 integration tests.
🤖 Generated with Claude Code