# GraphQL query depth and complexity limits

- **Pattern:** `ab-002196` (`saas-api-design.api-security.graphql-depth-limits`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-002196
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-002196)

## Why it matters

GraphQL without query depth and complexity limits is a denial-of-service vector with a trivially crafted payload: a malicious query that nests deeply-related types exponentially expands server-side resolution work while staying small on the wire (CWE-770, CWE-400). OWASP API 2023 flags this as unrestricted resource consumption (API4). A single deeply-nested query can saturate database connections and CPU for seconds, affecting all concurrent users. Without introspection controls, attackers also gain a complete map of your schema for free.

## Severity rationale

High because a single malicious query with exponential field nesting can exhaust server resources and produce a denial-of-service condition against all users of the API.

## Remediation

Add depth and complexity limits to your GraphQL server configuration before accepting public traffic. For Apollo Server:

```ts
import depthLimit from 'graphql-depth-limit'
import { createComplexityLimitRule } from 'graphql-validation-complexity'

const server = new ApolloServer({
  typeDefs,
  resolvers,
  validationRules: [
    depthLimit(6),
    createComplexityLimitRule(1000),
  ],
})
```

For GraphQL Yoga, use the built-in `useDepthLimit()` and `useQueryComplexity()` plugins. Set max depth to 5-7 (enough for most legitimate queries) and tune complexity to allow your most expensive real query with 20-30% headroom. Disable introspection in production if your API is not public: `introspection: process.env.NODE_ENV !== 'production'`.

## Detection

- **ID:** `graphql-depth-limits`
- **Severity:** `high`
- **What to look for:** If GraphQL is detected, enumerate all GraphQL server configurations and check whether query depth limits and complexity limits are enforced. Look for: `graphql-depth-limit` package usage, `graphql-query-complexity` package, Apollo Server `validationRules` with depth/complexity plugins, Yoga/Pothos built-in complexity config, or manual depth checking. Without these limits, a malicious query can nest deeply enough to exhaust server resources.
- **Pass criteria:** At least one of: query depth limit (e.g., max depth of 5-10) OR query complexity limit is enforced at the GraphQL server layer before execution.
- **Fail criteria:** GraphQL server accepts arbitrarily deep or complex queries with no validation rules applied.
- **Skip (N/A) when:** GraphQL is not used in the project. Signal: no `graphql`, `@apollo/server`, `graphql-yoga`, `pothos-graphql`, `nexus`, or `type-graphql` in `package.json`; no `.graphql` schema files; no GraphQL endpoint in routes.
- **Detail on fail:** Example: `Apollo Server configured with no validation rules`. Note what's missing (e.g., "Apollo Server configured with no validation rules; graphql-depth-limit and graphql-query-complexity not in dependencies"). Max 500 chars.
- **Remediation:** Add query depth and complexity limits to your GraphQL server configuration in `src/` or `app/api/graphql/`. For Apollo Server, use the `graphql-depth-limit` package: `validationRules: [depthLimit(5)]`. For complexity limiting, use `graphql-query-complexity` with a max complexity threshold appropriate to your schema. For GraphQL Yoga, use the built-in `useDepthLimit()` and `useQueryComplexity()` plugins. A reasonable starting point is max depth of 5-7 and a complexity limit that allows your most complex legitimate query with 20-30% headroom. Also consider disabling introspection in production if your API is not public.

---

## External references

- cwe CWE-770
- cwe CWE-400
- owasp A04

Taxons: cost-efficiency, injection-and-input-trust

HTML version: https://auditbuffet.com/patterns/ab-002196
