FlawOpen
●GraphQL API Security / CWE-400

GraphQL Query DoS: Depth, Cost & Batch Limits

How deeply nested, broad or batched GraphQL operations strain resolvers—and how to limit depth, cost, pagination and batch size.

💡 Plain English Explainer (ELI5)

Think of a GraphQL request as an order at a busy kitchen. A short order can still demand hundreds of dishes if it asks for huge lists or repeats expensive items. Depth limits help with nesting, but safe APIs also bound list sizes, operation cost and the number of operations processed together.

Core Concepts & Key Terms

Query depth
The maximum number of nested field levels in an operation. Depth does not measure how many objects a list field returns.
Query cost / complexity
A server-side estimate of resolver and data-source work, weighted by field behavior and list-size arguments, checked before execution.
Batch and breadth limits
Controls on operations per transport batch, top-level fields, aliases, or repeated selections. Enforce them at the request integration.
Schema introspection
Schema-discovery fields such as __schema and __type. Restricting them can reduce schema exposure but does not itself stop expensive operations.

How GraphQL Resource-Exhaustion Requests Work

Scope: Introspection is not required to submit costly queries; a client may already know the schema. This is a vulnerability class, not a specific CVE claim.

Step 1

Choose an exposed operation

A client selects nested relationships, large list arguments, repeated aliases, or multiple operations supported by the server.

Step 2

Multiply resolver work

Nested list fields can fan out; aliases can repeat expensive fields; a transport batch can carry several operations in one HTTP request.

Step 3

Exceed service budgets

Without bounds, resolvers and downstream databases may use excessive CPU, memory, connections, or time. Impact depends on schema, resolver behavior, data size and concurrency.

Step 4

Reject before expensive execution

Validate depth and structural limits, estimate cost using realistic list bounds, cap batch operations, and apply timeouts and caller-aware rate limits.

JavaScript / Apollo Server: Unbounded Operations vs. Guarded Validation

VULNERABLE PATTERN
// Vulnerable configuration: no application-specific demand controls.
import { ApolloServer } from "@apollo/server";

const server = new ApolloServer({
  typeDefs,
  resolvers,
  // Introspection is not what makes an expensive operation execute.
  introspection: true
});
HARDENED DEFENSE
// Apollo Server example with graphql-depth-limit installed.
import { ApolloServer } from "@apollo/server";
import depthLimit from "graphql-depth-limit";

const isProduction = process.env.NODE_ENV === "production";
const server = new ApolloServer({
  typeDefs,
  resolvers,
  // Schema-discovery policy, not a DoS control.
  introspection: !isProduction,
  validationRules: [depthLimit(8)],
  // Bound recursive fragment expansion before custom rules.
  maxRecursiveSelections: 10_000
});

// Also enforce pagination/argument caps, a cost budget,
// adapter-level batch-size limits, and execution timeouts.

Engineering Hardening Checklist

Depth and recursive-selection rules do not estimate database work by themselves. A shallow query can request enormous lists or alias an expensive resolver repeatedly. Set pagination caps, use schema-aware cost weights, limit batch operation count in the HTTP integration, and apply timeouts and per-user or per-key budgets. Disable introspection only if the product does not need it; this reduces schema discovery, not execution cost.

Framework mappings

← Browse Full Security Directory Explore Vulnerability Playbooks →