GraphQL is transforming how APIs are built. Instead of rigid REST endpoints, clients write exactly the query they need. It's powerful, flexible, and increasingly popular — adopted by GitHub, Shopify, Stripe, and thousands of startups.
It also introduces security risks that REST APIs don't have.
Why GraphQL Is Different
REST API Security Model
- •Fixed endpoints with predefined responses
- •Rate limit per endpoint is straightforward
- •Authorization checked per route/controller
- •Information exposure controlled by response serialization
GraphQL Security Model
- •Single endpoint (/graphql) accepting arbitrary queries
- •Query complexity varies wildly — hard to rate limit fairly
- •Authorization must be checked per field and per relationship
- •Introspection can reveal entire schema including internal types
1. Introspection Information Disclosure
mediumGraphQL's introspection feature lets clients query the entire API schema — every type, field, relationship, and argument:
Query the introspection endpoint
Send the standard introspection query: {__schema{types{name,fields{name,type{name}}}}} — most GraphQL servers respond with the full schema by default
Discover hidden functionality
The schema reveals every query and mutation, including internal ones: adminDeleteUser, debugResetDatabase, internalTransferFunds — fields the UI never uses but the API exposes
Map authorization boundaries
Understanding the full data model reveals IDOR opportunities, relationship traversals, and fields that contain sensitive data
⚠️Disable Introspection in Production
Introspection should be disabled in production. It's the equivalent of leaving your API documentation (including internal endpoints) publicly accessible. Every GraphQL framework has a configuration option to disable it.
2. Deeply Nested Queries (DoS)
GraphQL's relationship traversal creates unique denial-of-service vectors:
Query Complexity Attack
The problem
A query like: users { posts { comments { author { posts { comments { author { ... }}}}}}} grows exponentially. Each nesting level multiplies the number of database queries. 10 levels deep on a social graph can generate millions of queries from a single request.
Circular relationships
User has Posts, Post has Comments, Comment has Author (User). This circular reference allows infinite nesting — and infinite resource consumption.
Batch queries
GraphQL supports multiple queries in a single request via aliases or batching. An attacker can send 1000 expensive queries in one HTTP request, bypassing per-request rate limits.
Defenses:
- Query depth limiting — Reject queries deeper than N levels (typically 5-10)
- Query complexity analysis — Assign costs to fields and reject queries exceeding a budget
- Timeout per query — Kill queries that take longer than X seconds
- Rate limiting per complexity — Rate limit by estimated query cost, not just request count
3. Authorization Bypass via Field-Level Access
This is where GraphQL security diverges most from REST. In REST, each endpoint has a controller with explicit authorization. In GraphQL, authorization must be enforced on every field:
Query public data with private relationships
User queries their own profile (authorized) but traverses a relationship to access another user's private data: myProfile { organization { members { email, ssn, salary }}}
Mutations with insufficient checks
The updateUser mutation checks that you can update your own profile, but doesn't check individual fields — allowing you to set isAdmin: true or role: "superadmin"
Subscription data leakage
GraphQL subscriptions (real-time WebSocket connections) may not enforce the same authorization as queries — subscribing to events that include other users' data
As we covered in API Security: The Blind Spots Most Teams Miss, access control is the most common API vulnerability — and GraphQL makes it harder to get right because authorization must be enforced at the resolver level, not the route level.
🛑The Resolver Problem
Every field in GraphQL has a resolver function. Every resolver that returns sensitive data must check authorization. One missed check on one field in a chain of 50 is all it takes. This is fundamentally harder than securing 20 REST endpoints.
4. Injection Through GraphQL
GraphQL queries are strings that get translated into backend operations. Injection risks exist at every translation point:
GraphQL Injection Vectors
SQL injection via arguments
GraphQL arguments (filters, search terms, sort fields) are passed to database queries. If the resolver constructs SQL dynamically — which is common for flexible filtering — injection is possible.
NoSQL injection
Similar to SQL injection but with MongoDB operators ($ne, $gt, $regex) injected through GraphQL argument values.
Server-Side Request Forgery
Fields like profileImageUrl or webhookEndpoint that accept URLs and trigger server-side fetches — the same SSRF patterns from our SSRF article apply.
We covered modern injection techniques in our SQL injection post — GraphQL's flexible argument system makes ORM-level injection particularly relevant.
5. Batching Attacks
GraphQL Batching Abuse
Credential brute force
Send 1000 login mutations in a single request via aliases: q1: login(email: "victim@example.com", password: "password1"), q2: login(email: "victim@example.com", password: "password2"), ... This bypasses per-request rate limits.
IDOR enumeration
Query 1000 user profiles in a single request using aliases with different IDs. Extract data at 1000x the rate of individual requests.
OTP brute force
Attempt all 10,000 combinations of a 4-digit OTP in 10 batched requests of 1,000 each.
The Defense Checklist
Disable Introspection
Disable in production. If needed for tooling, restrict to authenticated internal users only.
Limit Complexity
Enforce query depth limits, complexity budgets, and per-query timeouts
Authorize per Field
Check authorization in every resolver that accesses sensitive data — not just at the query entry point
Limit Batching
Cap the number of operations per request. Rate limit by query complexity, not just request count.
How We Test GraphQL APIs
Our Web Security Auditor includes specialized GraphQL testing:
- Schema discovery — Even with introspection disabled, we use field suggestion brute-forcing and error-based enumeration to map the schema
- Authorization testing — Systematically test every field and relationship traversal for unauthorized access
- Injection testing — Test all arguments for SQL/NoSQL injection, SSRF, and other injection types
- DoS testing — Measure query complexity limits, batching limits, and timeout enforcement
- Human expert review — Our expert review tests complex business logic through GraphQL, including multi-step mutations and subscription abuse
As emphasized in our authentication bypass techniques, GraphQL mutations for login, password reset, and MFA need the same rigorous testing as REST endpoints — with the additional complexity of batching attacks.
Building or securing a GraphQL API? Our Web Security Auditor includes dedicated GraphQL testing, and our expert review goes deep on authorization logic and business-specific abuse scenarios. Secure your API.