CWE-776 / CWE-400

GraphQL Security: Query Depth Limits, Batching Attacks & Introspection Hardening

How recursive circular schemas, unmetered query complexity, and batching allow attackers to crash GraphQL backends with single HTTP requests.

💡 Plain English Explainer (ELI5)

In traditional REST APIs, if you ask for a user, you get a user. In GraphQL, the client decides what data to ask for. If your database allows users to have friends, and friends to have friends, an attacker can submit a nested query 500 layers deep: `user { friends { friends { friends... } } }`. The server attempts to join millions of database rows, exhausting all CPU and crashing the API for everyone.

Core Concepts & Key Terms

Query Depth
The number of nested relationship levels within a GraphQL document.
Query Cost / Complexity Analysis
Assigning numerical point values to fields and rejecting requests that exceed a maximum complexity threshold before execution.
GraphQL Batching Attack
Submitting an array of hundreds of queries inside a single HTTP POST request to bypass rate limiters.
Schema Introspection
A built-in GraphQL capability that allows clients to query `__schema` to discover all types, queries, and mutations.

Step-by-Step Attack Flow

Step 1

1. Introspection Discovery

An attacker queries { __schema { types { name fields { name } } } } to map the entire data graph.

Step 2

2. Circular Relationship Mapping

The attacker identifies self-referencing relationship fields (e.g., Thread -> comments -> replies -> author -> threads).

Step 3

3. Nested Query Crafting

A 1KB JSON payload is submitted containing 25 levels of nested circular queries.

Step 4

4. Resource Exhaustion (DoS)

The server creates thousands of nested database resolver promises, running out of memory (OOM) or tying up all database connections.

Source Code: Flaw vs. Secure Implementation

VULNERABLE PATTERN
# VULNERABLE: Unrestricted GraphQL Query Execution
# An attacker submits this 1KB payload that causes millions of DB joins:
"""
query CircularDoS {
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            friends {
              friends { name }
            }
          }
        }
      }
    }
  }
}
"""

# VULNERABLE APOLLO SERVER CONFIG
const { ApolloServer } = require("@apollo/server");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // CRITICAL: No depth validation, no complexity cost limits, introspection enabled!
  introspection: true 
});
HARDENED DEFENSE
// SECURE: Enforcing Query Depth Limiting & Cost Analysis
const { ApolloServer } = require("@apollo/server");
const depthLimit = require("graphql-depth-limit");
const { createComplexityLimitRule } = require("graphql-validation-complexity");

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // 1. Disable introspection in production environments
  introspection: process.env.NODE_ENV !== "production",
  
  // 2. Add validation rules executed BEFORE query runs
  validationRules: [
    // Reject any query deeper than 5 nested levels
    depthLimit(5),
    
    // Assign costs to fields (e.g. lists cost 10x) and reject if total > 1000
    createComplexityLimitRule(1000, {
      scalarCost: 1,
      objectCost: 2,
      listFactor: 10,
      onCost: (cost) => console.log(`Evaluated query complexity: ${cost}`)
    })
  ]
});

Engineering Hardening Checklist

← Browse Full Security Directory Explore Vulnerability Playbooks →