bug: bound GraphQL query depth and complexity to prevent unauthenticated DoS - #1066
Open
devin-ai-integration[bot] wants to merge 1 commit into
Open
devin-ai-integration[bot] wants to merge 1 commit into
devin-ai-integration[bot] wants to merge 1 commit into
Conversation
Comment on lines
+16
to
+25
| @Bean | ||
| public Instrumentation maxQueryDepthInstrumentation( | ||
| @Value("${graphql.limit.max-query-depth:10}") int maxQueryDepth) { | ||
| return new MaxQueryDepthInstrumentation(maxQueryDepth); | ||
| } | ||
|
|
||
| @Bean | ||
| public Instrumentation maxQueryComplexityInstrumentation( | ||
| @Value("${graphql.limit.max-query-complexity:50000}") int maxQueryComplexity) { | ||
| return new MaxQueryComplexityInstrumentation(maxQueryComplexity, pageSizeAwareCalculator()); |
Author
Comment on lines
+28
to
+30
| static FieldComplexityCalculator pageSizeAwareCalculator() { | ||
| return (FieldComplexityEnvironment environment, int childComplexity) -> | ||
| 1 + pageSize(environment.getArguments()) * childComplexity; |
Author
Comment on lines
+29
to
+30
| return (FieldComplexityEnvironment environment, int childComplexity) -> | ||
| 1 + pageSize(environment.getArguments()) * childComplexity; |
Author
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.
Summary
/graphqlispermitAlland the schema is cyclic (Article.comments->CommentEdge.node: Comment->Comment.article: Article!), while no graphql-java instrumentation was registered anywhere. A linear-sized anonymous query could therefore fan out exponentially — each nesting level ofcomments(first: 1000)triggers a paginated comment query plus onefindByIdper comment inArticleDatafetcher.getCommentArticle— exhausting the DB pool and heap.New
GraphQLQueryLimitConfigregisters twoInstrumentationbeans, which DGS's autoconfiguration folds into itsChainedInstrumentation, so the limits apply to every operation regardless of resolver:MaxQueryDepthInstrumentation(graphql.limit.max-query-depth, default 10)— a fullcomments -> edges -> node -> articlecycle costs 4 levels, so recursion terminates after ~2 cycles.MaxQueryComplexityInstrumentation(graphql.limit.max-query-complexity, default 50000, pageSizeAwareCalculator).The custom calculator is the important part: graphql-java's default counts 1 per field and is blind to the
first/lastfan-out, so afirst: 1000connection would cost the same asfirst: 1. Instead each field multiplies its children by its requested page size:So
articles(first: 1000) { ... comments(first: 1000) { ... } }costs ~1000 * 1000 * fields and is rejected, while realistic paging (articles(first: 20)withcomments(first: 20)~= 5.6k) stays well under the cap. Both limits are overridable viaapplication.properties.Not changed (out of scope for this finding):
/graphqlstays anonymous — the RealWorld spec requires unauthenticated reads — andCursorPageParameter.MAX_LIMITstays at 1000, since aggregate cost is now bounded by the complexity cap.Tests:
GraphQLQueryLimitConfigTestbuilds a minimal SDL reproducing the Article/Comment cycle and asserts the deep cyclic query and the wide 1000x1000 query are rejected, while a realistic 20x20 query passes both instrumentations.Verification caveat: this environment cannot resolve Gradle plugins/dependencies (Maven Central and plugins.gradle.org are off the session network allowlist and the Gradle cache is empty), so
compileJava/test/spotlessJavaCheckcould not be run locally; the repo also has no CI workflow. Formatting was matched to google-java-format by hand.Written by Devin
Devin-Org: engineering