GraphQL Security Deep Dive

Understand introspection leaks, query depth/complexity and batching attacks, why authorization must be per-field, and persisted queries as production lockdown.

Hard 70m 3 tasks

Learning Objectives

  • Explain how GraphQL's single-endpoint, client-specified-query model differs from REST security-wise
  • Explain why introspection can leak the entire API schema, including undocumented fields
  • Describe query depth/complexity attacks and batching attacks as GraphQL-specific DoS vectors
  • Explain why authorization must be enforced per-field, not just per-endpoint, in GraphQL
  • Describe persisted queries as a defense against arbitrary client-specified queries in production

How GraphQL Changes the Security Model

REST exposes many endpoints, each returning a fixed shape of data, each individually securable. GraphQL exposes one endpoint, and the client specifies exactly which fields it wants in the query itself. This flexibility is GraphQL's core value proposition — and also the source of nearly every GraphQL-specific vulnerability class, because a single endpoint can no longer be secured by URL path alone.

Introspection: The Schema Leak

GraphQL supports introspection — a query that asks the API to describe its own entire schema: every type, every field, every mutation, including ones never referenced by the official frontend. Left enabled in production, introspection hands an attacker a complete API map for free, including fields that were "hidden" only by not being used in the UI (security through obscurity, which introspection defeats instantly).

{
  __schema {
    types { name fields { name } }
  }
}

Query Depth and Complexity Attacks

Because GraphQL lets a client nest queries arbitrarily, a maliciously deep or wide query can force the server to do an enormous amount of work in response to a single request:

{
  user { friends { friends { friends { friends { name } } } } }
}

Each additional nesting level can multiply the underlying database work exponentially — a classic query depth denial-of-service. Query complexity analysis assigns a cost to each field/nesting level and rejects queries above a threshold, rather than just limiting raw depth.

Batching Attacks

GraphQL (and some REST APIs) support sending an array of operations in a single request. Without rate limiting per operation inside the batch (not just per HTTP request), an attacker can submit thousands of login attempts, password-reset requests, or lookups in one single request that a naive rate limiter sees as "one request."

Authorization Must Be Per-Field, Not Per-Endpoint

Because a single GraphQL query can request data across many different types and relationships in one round trip, authorization checks applied only "at the top" (e.g. "is this user logged in") miss the more important question: does this user have permission to see this specific field on this specific object? A query for user(id: 123) { email, salary, ssn } needs field-level authorization on salary and ssn independent of whether the user is allowed to query user at all.

Persisted Queries: Locking Down Production

A persisted query allowlist stores the exact set of queries the legitimate frontend actually uses (usually as a hash), and the server rejects any query at request time that isn't on that pre-approved list. This closes off the "client can send any arbitrary query" attack surface entirely in production, while still allowing full flexibility during development.

Common Pitfalls

  • Leaving introspection enabled in production "because it's convenient for debugging"
  • Assuming REST-style top-level authentication is sufficient without field-level authorization checks
  • Rate-limiting only at the HTTP-request level, missing that a single GraphQL request can contain many effective "operations" via batching or nested queries
  • Treating GraphQL as inherently safer than REST just because it's newer — it has an entirely different, GraphQL-specific vulnerability surface, not a smaller one

Introspection is a built-in GraphQL feature that describes the schema itself, including fields the official frontend never uses.

✦ Answer the questions to complete this task

What does an enabled introspection query expose to an attacker?

Nested relationship lookups can compound the backend work required to answer a single query.

✦ Answer the questions to complete this task

Why is an arbitrarily deep, nested GraphQL query dangerous?

A single GraphQL query can mix public and private fields on the same object in one round trip.

✦ Answer the questions to complete this task

Why is per-endpoint authorization (like REST typically uses) insufficient for GraphQL?

💪 Exercises & Challenges

📝 MCQ Hard +20 XP

GraphQL Security Deep Dive MCQ

Test your understanding of GraphQL Security Deep Dive.

Start →
⚙️ Practical Hard +35 XP

Design a GraphQL Rate-Limiting and Authorization Strategy

A GraphQL API allows nested queries and batched operations, and currently rate-limits only at the HTTP-request level. Design a strategy addressing: (1) query complexity limiting, (2) per-operation rat

Start →
🚩 Challenge Hard +60 XP

Trace the Introspection-to-Takeover Path

A bug bounty report reads: 'The GraphQL endpoint at /graphql has introspection enabled. Using the schema, we found a mutation resetPassword(userId: ID!, newPassword: String!) that is not referenced an

Start →