Skip to content
API Design Learn/GraphQL Real-Time & Security
Browsing as a guest. Sign in to save your progress and earn XP as you complete chapters.

Guarding Against Malicious Queries

8 min read

You'll learn to

  • -Implement query depth limiting and complexity analysis to prevent a single malicious query from overwhelming the server
  • -Decide when to disable introspection in production without breaking legitimate developer tooling

GraphQL's flexibility - letting a client construct arbitrary, deeply nested queries against the schema's full type graph - is also its biggest attack surface. A schema that lets `User -> friends -> friends -> friends...` nest indefinitely gives a malicious (or just careless) client the ability to construct a single query that costs the server exponentially more to execute than a typical request.

Depth Limiting

An unbounded, exponentially expensive query the schema should reject
query MaliciousDepthBomb {
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            friends { name }  # nesting indefinitely deeper...
          }
        }
      }
    }
  }
}

Depth limiting rejects a query outright if its nesting exceeds a configured maximum (commonly somewhere around 7-10 levels, tuned to what legitimate queries actually need) before execution ever begins - it's a coarse but cheap first line of defense, since checking nesting depth against the raw query document costs almost nothing compared to actually resolving even one level of a deeply nested query.

Complexity Analysis: A Finer-Grained Budget

Assigning each field a cost, and rejecting queries that exceed a total budget
type Query {
  users(first: Int): [User!]!   # cost scales with 'first' - fetching 1000 is expensive
}
# A complexity analyzer estimates cost per field (often "1 + cost of nested
# fields, multiplied by any 'first'/'limit' argument") and sums it across the
# whole query, rejecting anything over a configured budget - e.g. 1000 points -
# BEFORE execution.

Depth alone doesn't capture cost precisely - a shallow query requesting `users(first: 10000)` can be far more expensive than a deeply nested one touching a handful of records at each level. Complexity analysis assigns a cost to each field (often scaled by pagination arguments like `first`) and rejects a query whose total estimated cost exceeds a budget, catching the "wide" attack pattern depth limiting alone would miss.

Introspection: Useful for Developers, Risky in Production

GraphQL's introspection system lets a client query the schema itself - every type, every field, every argument - which is genuinely valuable for developer tooling (autocomplete, schema-aware IDEs, generated documentation) during development. In production, though, it hands an attacker a complete, structured map of the entire API surface with no effort required to discover it manually, which is why many production GraphQL APIs disable introspection entirely for unauthenticated or non-internal callers, while keeping it available in development and staging environments where the tooling benefit still applies.

Persisted queries (where a client sends a hash of a pre-approved query instead of the full query text) are a stronger defense than complexity limits alone for public APIs with known client applications - the server only ever executes queries it has explicitly allow-listed in advance, eliminating the arbitrary-query attack surface for any caller using the persisted-query mechanism.

Interview Signal is part of Pro

See a real weak answer next to a real strong one for this exact topic.

Quiz is part of Pro

Test what you just read with a short quiz, and bank the XP.

Ready to Build This?

Design Query Fortress in the API Design Lab's GraphQL Mastery act.

ScaleDojo Logo
Initializing ScaleDojo