Back to Blog
API SecurityGraphQLWeb Security

GraphQL Security: The Unique Risks of a Flexible API

January 23, 20266 min readRedVolt Team

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

medium

GraphQL's introspection feature lets clients query the entire API schema — every type, field, relationship, and argument:

1

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

2

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

3

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:

1

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 }}}

2

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"

3

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

01

Disable Introspection

Disable in production. If needed for tooling, restrict to authenticated internal users only.

02

Limit Complexity

Enforce query depth limits, complexity budgets, and per-query timeouts

03

Authorize per Field

Check authorization in every resolver that accesses sensitive data — not just at the query entry point

04

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:

  1. Schema discovery — Even with introspection disabled, we use field suggestion brute-forcing and error-based enumeration to map the schema
  2. Authorization testing — Systematically test every field and relationship traversal for unauthorized access
  3. Injection testing — Test all arguments for SQL/NoSQL injection, SSRF, and other injection types
  4. DoS testing — Measure query complexity limits, batching limits, and timeout enforcement
  5. 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.

Want to secure your application or smart contract?

Request an Expert Review