Schema Security: Depth Limits, Complexity Budgets & Attack Surfaces
Every field in your GraphQL schema is a potential attack surface — recursive traversal enables DoS, alias amplification enables field-level abuse, and introspection enables reconnaissance. Depth limits, complexity budgets, and persisted operations are the minimum viable protection.
GraphQL Backend & API Design
Schema Security: Depth Limits, Complexity Budgets & Attack Surfaces
You own the schema and the resolvers — the schema is a public contract you can never silently break, and every resolver is a performance commitment you make on every query. The attack surface of a GraphQL API begins with the schema itself: a single introspection query reveals every type, every field, every argument, and every relationship in your graph. From that map, an attacker can construct queries that your application never intended to handle — recursive queries that exhaust memory, alias-amplified mutations that bypass rate limits, and argument permutations that probe your resolver error paths. The discipline of GraphQL security is not firewall rules and WAF signatures; it is schema design decisions that structurally prevent these queries from being expressed.
This is Part 6 of the GraphQL Backend & API Design series. It connects to Part 1 (Schema Design) for the @requiresScopes directive, and to Part 7 (Schema Evolution) for persisted operations as an evolution-safe security boundary.
1. The Attack Surface Taxonomy
Three categories of GraphQL-specific attack vectors exist before any application-layer logic runs:
1.1 Recursive Traversal DoS
Without a depth limit, this query executes until the process runs out of memory or the DB connection pool is exhausted. One client, one query, one process crash.
1.2 Alias Amplification
Per-field rate limiting counts user as "1 call." Alias amplification fires 100 resolver executions while looking like 1 field to naïve rate limiters.
1.3 Introspection Reconnaissance
The introspection result reveals every internal type name, every admin-only mutation, every argument, and every @deprecated field — a complete map for a targeted attack.
2. The Three-Gate Defense Model
Effective GraphQL security requires three sequential validation gates, each blocking a different attack class:
2.1 Gate 1: Query Depth Limit
Choosing the right depth limit: audit your actual client queries. Most production UIs have a maximum natural depth of 5–6 (root → entity → relationship → sub-field → scalar). Setting the limit at 7 allows all legitimate queries while blocking recursive amplification.
2.2 Gate 2: Complexity Budget
2.3 Gate 3: Persisted Operations Manifest
Persisted operations are the strongest security gate: in production, only pre-registered operation documents are allowed to execute. Any novel query — including all attack queries — is rejected without parsing or execution.
Persisted operations break the assumption that clients send arbitrary GraphQL documents. In safelisted mode, no unregistered query can execute — recursive traversal attacks, alias amplification, and introspection recon are all structurally impossible, not just rate-limited.
3. Introspection Access Control
Alternatively, with Apollo Router's @requiresScopes:
4. graphql-armor v3 — Drop-In Security Middleware
graphql-armor v3 is a plugin for graphql-yoga, apollo-server, and envelop that enables all three security gates with one configuration object:
5. Rate Limiting by Operation Name
IP-based rate limiting is insufficient for GraphQL — the same IP can send high-cost and low-cost operations. Rate limit by operationName instead:

Three sequential validation gates prevent the three main attack classes: depth limits block recursive traversal DoS, complexity budgets block alias amplification, and persisted operations safelisting blocks all novel attack queries before parsing.

Persisted operations alone provide the highest security guarantee but require build-time coordination. Defense-in-depth with all three gates is the production standard: each gate catches a different attack class.
Summary
| Concept | Rule |
|---|---|
| Introspection | Disable in production for unauthenticated requests — the introspection response is a complete attack map. Allow for authenticated internal tools and developer environments only. |
| Depth limit | Set to your application's maximum natural query depth + 2. Most production UIs have a natural depth of 5–6; a limit of 7–8 blocks all recursive attacks while allowing all legitimate queries. |
| Complexity budget | Per-field cost × depth factor prevents alias amplification and expensive field combinations. maxAliases: 15 in graphql-armor is a direct alias amplification blocker. |
| Persisted operations | In safelisted mode, only pre-registered operation documents execute — recursive attacks, alias amplification, and introspection recon are structurally impossible regardless of depth or complexity limits. |
| Rate limit by operationName | IP-based rate limiting is bypassed by operation mixing. Key rate limiters on userId:operationName to enforce per-operation fairness. |
What's Next
Part 7 — Schema Evolution: Breaking Changes, Deprecation & Migration Windows covers the full lifecycle of a field from birth to removal: the additive-only rule, @deprecated sunset message format, CI-enforced breaking change detection with rover graph check, and the phased migration playbook that makes field removal safe.
This article was developed with AI-assisted deep search, specification cross-referencing, and technical research synthesis.