Skip to content

fix(graphql-server): bound introspection depth with its own fixed ceiling - #1818

Merged
pyramation merged 1 commit into
mainfrom
fix/introspection-depth-ceiling
Sep 8, 2026
Merged

fix(graphql-server): bound introspection depth with its own fixed ceiling#1818
pyramation merged 1 commit into
mainfrom
fix/introspection-depth-ceiling

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #1817, addressing the review finding at https://github.com/constructive-io/constructive/pull/1817/changes#r3962532563.

#1817 skipped the __schema/__type subtree entirely, but the introspection schema is recursive (__Type.fields → __Field.type → __Type), so { __schema { types { fields { type { fields { type … } } } } } } could nest without limit and still report { depth: 1, cost: 0 }.

The gate now walks the introspection subtree again, but measures it against a fixed INTROSPECTION_MAX_DEPTH = 16 (relative to the introspection field) instead of the tenant's maxQueryDepth:

type DepthBound = { kind: 'query' } | { kind: 'introspection'; base: number };
// walkSelectionSet(..., bound): query → maxQueryDepth as before;
// introspection → depth - base > INTROSPECTION_MAX_DEPTH ⇒ QUERY_TOO_DEEP

The standard introspection document (graphql-js getIntrospectionQuery with all options) is 13 deep, so codegen/tooling still passes under any tenant depth budget, while runaway recursive introspection is rejected. Introspection depth is not reported in analysis.depth and cost stays 0 (no connections in introspection types). Tests cover the recursive shape being rejected at 9 hops and accepted at 6.

Link to Devin session: https://app.devin.ai/sessions/1c0e1e05988944c2995bd82bb7e0c215
Open in Devin Desktop: https://app.devin.ai/desktop/session/1c0e1e05988944c2995bd82bb7e0c215?variant=devin
Requested by: @pyramation

…ling

The introspection subtree was skipped entirely by the gate, so a recursive
__schema { types { fields { type { fields ... } } } } document could nest
without limit. Walk it again, but against INTROSPECTION_MAX_DEPTH (16) rather
than the tenant's maxQueryDepth, so the standard introspection document (13)
still passes while runaway recursion is rejected.
@devin-ai-integration

Copy link
Copy Markdown
Contributor

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@tenki-reviewer

tenki-reviewer Bot commented Sep 8, 2026

Copy link
Copy Markdown

Review complete. No issues found — approved ✅.


This change hardens the document gate's handling of schema introspection in graphql/server/src/protection/document-gate.ts. Previously introspection selections were skipped entirely and walked with no depth bound; because the introspection schema is recursive (__Type.fields → __Field.type → __Type), a client could nest that shape arbitrarily deep with no protection. The gate now walks introspection subtrees but measures them against a fixed INTROSPECTION_MAX_DEPTH = 16 ceiling (relative to the depth where the introspection field began) instead of the tenant's maxQueryDepth, preserving the intent that normal tooling (graphql-js's deepest introspection query reaches 13) is unaffected while runaway recursion is refused with the existing QUERY_TOO_DEEP error. A new DepthBound union threads the active bound (query vs introspection) through the recursion, and it is correctly preserved across inline fragments and fragment spreads and reset only when a root-level __schema/__type field is entered. A test covers both the rejection and the no-throw boundary.

Files Change
graphql/server/src/protection/document-gate.ts Introduce INTROSPECTION_MAX_DEPTH, DepthBound, and walk/bound introspection subtrees under the fixed ceiling.
graphql/server/src/protection/__tests__/document-gate.test.ts Add cases asserting deep recursive introspection is rejected while shallower nesting stays within budget.

Reviewed commit: 8d20e5c

@pyramation
pyramation merged commit 7d28f71 into main Sep 8, 2026
21 checks passed
@pyramation
pyramation deleted the fix/introspection-depth-ceiling branch September 8, 2026 23:22
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.

1 participant