1. Circular Query Denial of Service (DoS) & Depth Limiting

In traditional REST APIs, backend engineering teams maintain absolute control over the complexity of data retrieval operations: a client requesting GET /users/1 receives a predefined JSON representation of that user, and relational joins are carefully constrained within backend SQL queries. In stark contrast, GraphQL shifts structural query control directly to the client. The client constructs an Abstract Syntax Tree (AST) declaring precisely which relational entities, child collections, and nested fields the server should resolve and return.
When a GraphQL schema models bidirectional relationships - such as a User possessing many Posts, and each Post referencing an Author (which is of type User) - an adversary can craft a deeply nested recursive query. By chaining user { posts { author { posts { author { ... } } } } } across dozens or hundreds of nesting levels, an attacker forces the GraphQL execution engine into an exponential database retrieval loop. If the query reaches a depth of only 10 or 15 levels, the server may generate millions of recursive SQL queries or saturate memory buffers attempting to construct the gigantic nested JSON payload in RAM. This triggers instantaneous CPU exhaustion, database connection pool starvation, and application server crashes (Denial of Service).
The primary engineering defense against circular query attacks is implementing strict Query Depth Limiting at the AST validation phase, prior to resolver execution. By utilizing validation rules like graphql-depth-limit, the server inspects the query document's abstract syntax tree. If the maximum nesting depth exceeds a defined operational threshold (typically 5 to 7 levels for enterprise applications), the GraphQL server rejects the entire query immediately with an HTTP 400 Bad Request, before a single database query is dispatched. In addition to depth limiting, high-scale GraphQL APIs implement Query Complexity Analysis (assigning computational cost weights to expensive relational fields) to reject queries that exceed a maximum complexity budget.
Audit your GraphQL endpoints and test query depth protections using the WeSecureX Website Vulnerability Scanner and explore our masterclass on API Rate Limiting Defense.
// Enforcing Query Depth Limiting in Apollo Server / Node.js
import { ApolloServer } from '@apollo/server';
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(6)], // Rejects AST trees deeper than 6 levels
});2. GraphQL Batching Attacks: Bypassing Rate Limits

GraphQL provides built-in mechanisms that allow clients to consolidate multiple distinct operations into a single network payload. While this dramatically improves mobile performance by reducing cellular latency round-trips, cybercriminals routinely weaponize batching to bypass traditional perimeter security controls and execute high-speed brute-force attacks.
GraphQL batching attacks manifest through two primary technical vectors:
- Array Batching: The client submits an HTTP POST request whose JSON payload is an array of separate GraphQL query documents: [{"query": "..."}, {"query": "..."}]. The server parses the array and resolves each query sequentially or concurrently.
- Alias Batching (Single-Query Multi-Invocation): Within a single GraphQL query or mutation, an attacker defines multiple aliases pointing to the identical mutation field: mutation { a1: login(user: "admin", pass: "123") { token } a2: login(user: "admin", pass: "456") { token } ... a500: login(user: "admin", pass: "xyz") { token } }.
Because traditional Web Application Firewalls (WAFs) and standard reverse proxy rate limiters operate at the HTTP layer, they observe only a single incoming HTTP POST request to /graphql from that IP address. The attacker can execute 500 credential stuffing attempts or coupon enumeration attempts within that single request without tripping standard threshold rate limiters (e.g., 20 requests per minute).
To defend against batching attacks, GraphQL gateways must implement Operation-Level Rate Limiting and AST Alias Counting. Gateway middleware must parse incoming ASTs and enforce strict caps on the maximum number of aliased fields permitted in a single operation (e.g., maximum 5 aliases). Furthermore, sensitive mutations - such as login, password reset, and payment authorization - must track attempt counters inside Redis by user account identifier, rejecting subsequent executions regardless of whether they arrive in batched or isolated requests.
Audit your GraphQL gateway against batching attacks using the WeSecureX Surface Recon Engine and test endpoint rate limits with our API Security Testing Guide.
// GraphQL Alias Batching Mitigation Middleware
import { GraphQLError } from 'graphql';
export function aliasLimiter(maxAliases = 5) {
return (context: any) => ({
Field(node: any) {
if (node.alias) {
context.aliasCount = (context.aliasCount || 0) + 1;
if (context.aliasCount > maxAliases) {
throw new GraphQLError(`Too many aliased fields. Maximum permitted: ${maxAliases}`);
}
}
}
});
}3. Broken Object Level Authorization (BOLA) in Field Resolvers

A widespread architectural vulnerability in GraphQL implementations is assuming that top-level query authentication automatically protects all nested relational fields. In REST APIs, each endpoint (e.g., /api/v1/invoices/:id) is governed by dedicated route middleware where authorization is explicitly evaluated. In GraphQL, because clients can query deeply nested relational paths (e.g., query { user { organization { invoices(id: "inv_999") { total amount } } } }), authorization checks must be enforced at the individual Field Resolver level.
When developers write naive field resolvers that query backend database entities directly by an incoming argument identifier - without verifying that the authenticated user possesses ownership of that record - an Insecure Direct Object Reference (BOLA) is introduced. An attacker can authenticate as User A, submit a query requesting invoice(id: "inv_tenant_b_104"), and retrieve confidential billing records belonging to Tenant B. Because the top-level query passed authentication, the execution engine happily resolves the child field.
To eliminate BOLA in GraphQL:
- Contextual User Injection: Authentication middleware must verify incoming JWT or session credentials at the HTTP gateway, injecting the authenticated user object, tenantId, and role permissions into the GraphQL execution context object (the third parameter in resolver functions).
- Database Ownership Scoping: Field resolvers must never query database tables by primary key alone. Every database query executed inside a resolver must incorporate tenantId: context.user.tenantId and userId: context.user.id within the query predicate.
- Declarative Schema Directives: High-maturity organizations implement custom GraphQL schema directives (such as @auth(requires: USER, owns: "id")) that automatically enforce object ownership rules declaratively before resolver execution.
Audit your resolver authorization boundaries using the WeSecureX Vulnerabilities Catalog and discover exposed attack surfaces on Attack Surface Management.
// VULNERABLE Resolver Pattern
const resolvers = {
Query: {
invoice: async (_, { id }) => {
// Broken: Fetches invoice by ID without checking if current user owns it!
return await db.invoice.findUnique({ where: { id } });
}
}
};
// HARDENED Resolver Pattern
const resolvers = {
Query: {
invoice: async (_, { id }, { user }) => {
if (!user) throw new GraphQLError('Unauthorized');
return await db.invoice.findFirst({
where: { id, userId: user.id } // Scoped strictly to authenticated user!
});
}
}
};4. Disabling Schema Introspection in Production

GraphQL features a built-in introspection system that allows clients to query the schema itself using special meta-fields like __schema and __type. Introspection returns a complete, machine-readable blueprint of your entire GraphQL API: every object type, every query, every mutation, every parameter type, every field description, and deprecated experimental endpoints.
While introspection is an extraordinary productivity accelerator during local development - enabling tools like GraphQL Playground, GraphiQL, and automated TypeScript SDK generators - leaving introspection enabled in production environments presents a massive security vulnerability. Introspection provides adversaries with full visibility into your internal data models, administrative mutations (e.g., adminDeleteUser, updateBillingPlan), hidden feature flags, and backend naming conventions. Attackers use automated tools like GraphQL Voyager or InQL to map the entire schema in seconds, identifying unauthenticated administrative mutations and undocumented BOLA vectors without guessing a single endpoint.
Production GraphQL configurations must unconditionally disable schema introspection in non-development environments:
- Apollo Server: Set introspection: false (or conditionally introspection: process.env.NODE_ENV !== 'production').
- Yoga / Envelop: Disable the useDisableIntrospection plugin for public traffic.
- Fallback Error Messages: Ensure that queries targeting __schema return a standard validation error (such as 'GraphQL introspection is disabled') rather than a verbose error disclosing internal server details.
Test your public endpoints and verify that introspection is disabled using the WeSecureX Free Tools Suite and audit header security with our Security Headers Checker.
Always disable GraphQL introspection in production environments. Leaving introspection enabled provides attackers with a complete structural blueprint of all queries, mutations, and database models.
Apply it with WeSecureX
Add a hostname you own or are allowed to test, run a scan, and open the findings for that target. A confirmed result includes the request WeSecureX sent and the response the site returned, so you can reproduce it, fix it in your own application, and rescan the same host to confirm the issue is gone. WeSecureX is automated testing, not a manual penetration test, and a clean report is not a promise that every weakness is gone.
WeSecureX modules for this topic
Checks that belong with this topic
- Enforce query depth limits (max 5-7 levels) across all GraphQL endpoints.
- Disable schema introspection in production environments.
- Implement operation-level rate limiting on aliased query and mutation batching.
- Enforce tenant ownership authorization checks inside every field resolver.
- Strip verbose database error messages and stack traces from GraphQL responses.
- Audit GraphQL endpoints continuously in the [WeSecureX Vulnerabilities Manager](/vulnerabilities).
Actionable Checklist
WeSecureX Platform · Scanner Engine
Automated Vulnerability Scanner
Launch targeted or full-domain security audits to detect OWASP Top 10 vulnerabilities automatically.
Includes: OWASP Top 10 automated test suites · Real-time severity scoring (CVSS v3.1) · Instant remediation code snippets
Frequently Asked Questions
Query complexity assigns numeric weights to schema fields based on computational cost (e.g. scalar fields cost 1 point, relations cost 10 points) and rejects queries exceeding a total threshold before execution.
Traditional WAFs count incoming HTTP requests. A single HTTP POST can contain hundreds of aliased mutations, allowing attackers to brute-force passwords while staying under HTTP request limits.
Launch an automated audit on your GraphQL endpoint using the [WeSecureX Vulnerability Scanner](/scanner).
Authoritative Sources & References
- OWASP GraphQL Cheat SheetOWASP (official)View Source
Related Guides
Continue exploring related technical architecture and defensive guides
API · BOLA
API Security in 2026: The Biggest API Vulnerabilities and How to Prevent Them
APIs are the product. Focus on object-level auth, least data, strong authn, and automated testing in 2026.
API security · REST
REST API Security Best Practices for Modern Applications
Developer blueprint for securing REST APIs: BOLA/IDOR prevention patterns, asymmetric JWT validation, Redis token bucket rate limiting, and strict input DTOs.
IDOR · BOLA
Broken Access Control & IDOR: Complete Developer Guide
Insecure Direct Object References (IDOR) and Broken Access Control rank as the #1 web application risk. Learn how to scope queries and fix authz flaws.