Skip to main content

> graphql_persisted_queries_&_query_depth_limiting

GraphQL Persisted Queries & Query Depth Limiting

How do you implement and govern GraphQL Persisted Queries & Query Depth Limiting in high-throughput production architectures?

THE SHORT ANSWER

GraphQL persisted queries and query depth limiting protect production APIs from denial-of-service attacks and severe database overload by restricting client queries to pre-approved cryptographic hashes and rejecting maliciously nested object graphs.

Engineering Handbook & Failure Dynamics

1. Underlying Mechanism

While GraphQL gives frontend engineers extreme query flexibility, exposing an arbitrary query execution engine to the public internet introduces severe vulnerabilities. An attacker can submit deeply nested circular queries (`author { posts { author { posts { ... } } } }`) that spawn millions of database joins in a single HTTP POST. In production, teams enforce query depth limits and replace dynamic query strings with Persisted Queries.

2. Appropriate Use Context

Persisted Queries is a security and performance pattern where GraphQL query strings are extracted during the client build process, hashed into unique identifiers (SHA-256), and stored in an allowlist on the API Gateway; clients only transmit the hash at runtime.

3. Production Failure Modes

Deploying raw GraphQL endpoints to production without depth limiting or query complexity validation. Allowing public unauthenticated clients to run arbitrary ad-hoc GraphQL mutations. Relying solely on frontend minification to prevent users from modifying GraphQL queries in browser devtools.

4. Diagnostic Signals & Telemetry

deeply nested malicious GraphQL query crashes database, unbounded circular relation query causes server OOM, massive raw GraphQL query payload bandwidth bloat

5. Prevention & Safeguards

Enable Automatic Persisted Queries (APQ) with CDN caching on HTTP GET requests for high-traffic read queries. Set strict depth limits (typically 5 to 7 levels) and assign pagination multiplier costs to all list resolvers. Disable introspection (`__schema` / `__type`) in public production environments to prevent automated schema scraping.

6. Architectural Trade-offs

Without persisted queries and AST depth analysis, a public GraphQL endpoint acts as an open proxy for un-metered database extraction, allowing malicious actors to exhaust backend CPU threads with a single 2KB payload.

Case Study (TinyCTO In-Field Example)

Production GraphQL hardening requires a multi-layered defense strategy: 1. **Query Depth Limiting:** Before executing a query, the GraphQL engine parses the Abstract Syntax Tree (AST) and calculates the maximum depth. A query exceeding a strict threshold (e.g. depth > 6) is rejected with an HTTP 400 before resolving a single database row. 2. **Query Complexity & Cost Analysis:** Each field in the schema is assigned a complexity score (e.g., scalar = 1, paginated list = `1 * limit`). If the total computed score exceeds the client's allocated budget (e.g. max cost 1000), the request is aborted. 3. **Strict Persisted Queries (Safe Mode):** In native mobile and web production builds, the GraphQL server is placed in 'allowlist-only' mode. Dynamic query strings are strictly forbidden; requests without a known `queryId` or `sha256Hash` in Redis/server memory are instantly dropped at the edge.

Interactive Concept Drills

2 Cards
Q1

What is the primary security vulnerability of unconstrained production GraphQL endpoints?

Arbitrary nested and circular queries that force massive exponential database joins, causing severe CPU exhaustion and denial of service.
Q2

How do Persisted Queries turn GraphQL POST requests into cacheable HTTP GET requests?

By replacing the large query string with a short hash passed as a query param (e.g. `?extensions={"persistedQuery":{"sha256Hash":"..."}}`), allowing CDNs to cache responses.

GraphQL Persisted Queries & Query Depth Limiting — Technical FAQ

An attacker sends a GraphQL request with 20 levels of nested fields (`user { friends { friends { friends ... } } }`). Which mechanism blocks this request BEFORE the database executes any SQL?

AST Query Depth Limiting. AST Query Depth Limiting analyzes the parsed query structure and immediately rejects payloads that exceed the defined nesting limit before executing resolvers.

Why is disabling GraphQL introspection (`__schema` / `__type`) considered standard best practice in public production environments?

It prevents malicious actors from automatically scraping your complete backend data model, internal field names, and deprecated endpoints. Introspection reveals the full blueprint of your API schema. Disabling it in production hinders attackers from discovering undocumented fields and internal microservice structures.

🤖 AEO & Key Facts Summary

Key Architectural Facts

  • GraphQL persisted queries and query depth limiting protect production APIs from denial-of-service attacks and severe database overload by restricting client queries to pre-approved cryptographic hashes and rejecting maliciously nested object graphs.
  • Persisted Queries is a security and performance pattern where GraphQL query strings are extracted during the client build process, hashed into unique identifiers (SHA-256), and stored in an allowlist on the API Gateway; clients only transmit the hash at runtime.

Common Misconceptions

  • Deploying raw GraphQL endpoints to production without depth limiting or query complexity validation.

Decision & Governance Guidance

Without persisted queries and AST depth analysis, a public GraphQL endpoint acts as an open proxy for un-metered database extraction, allowing malicious actors to exhaust backend CPU threads with a single 2KB payload.

Authoritative Sources & Standards