Why GraphQL Has a Unique Security Profile

REST APIs have multiple endpoints, each with explicit inputs and outputs. GraphQL exposes a single endpoint where clients specify exactly what data they want β€” including relationships, nested objects, and computed fields. This flexibility creates security challenges that traditional REST scanning tools don't cover.

The schema-driven nature means a complete map of your data model is potentially just one introspection query away. The flexible query system means attackers can craft expensive queries that exhaust server resources. Authorization mistakes at the resolver level affect all queries that touch that data.

Introspection Exposure

GraphQL's introspection system allows any client to query the entire schema β€” all types, fields, mutations, and their descriptions. In development, this powers IDE autocompletion. In production, it hands attackers a complete map of your data model.

introspection-probe.graphql GraphQL
# Standard introspection query β€” reveals entire schema
{
  __schema {
    types {
      name
      fields {
        name
        type { name }
        description
      }
    }
  }
}

Disable introspection in production. Most GraphQL servers support this with a single config option. For development, require authentication to run introspection queries.

graphql_config.py Python (Strawberry)
import strawberry
from strawberry.extensions import DisableIntrospection

schema = strawberry.Schema(
    query=Query,
    extensions=[
        DisableIntrospection  # disable in production
    ]
)

Batching Attacks and Brute Force

GraphQL supports query batching β€” sending multiple operations in a single HTTP request. An attacker can use this to bypass rate limiting. Instead of sending 1000 login requests (blocked at 5/minute), they send 10 requests each containing 100 batched login mutations.

batching-attack.json JSON
[
  { "query": "mutation { login(user:\"admin\", pass:\"pass001\") { token } }" },
  { "query": "mutation { login(user:\"admin\", pass:\"pass002\") { token } }" },
  // ... 998 more attempts in one HTTP request
]

Mitigations: disable query batching, or limit the number of operations per batch. Apply rate limiting at the operation level, not just the HTTP request level.

Deeply Nested Query DoS

GraphQL allows arbitrarily deep queries across relationships. An attacker can craft a query that traverses circular references millions of levels deep, consuming exponential server resources:

nested-query-dos.graphql GraphQL
# author.posts.author.posts.author.posts... β€” exponential cost
{ author(id: "1") {
    posts { author { posts { author { posts {
      author { posts { author { posts { title } } } }
    } } } }
  }
}

Implement query depth limiting (typically max depth 5-10) and query complexity analysis. Libraries like graphql-depth-limit (JS) or equivalent for your stack handle this.

Authorization Flaws in Resolvers

In REST, authorization is typically enforced at the endpoint level. In GraphQL, authorization must be enforced at the resolver level β€” every field that returns sensitive data needs an authorization check. Missing one resolver means that data is accessible via any query that includes that field.

Common mistake: Checking authorization at the query root but not in nested resolvers. A user who can query { post(id: 1) { title } } might be able to also retrieve { post(id: 1) { author { email phone } } } if the author resolver doesn't check permissions.

Injection in Resolvers

GraphQL arguments are passed to resolvers, which then query databases or call other services. If resolver code constructs raw queries from GraphQL arguments, all the same injection vulnerabilities apply β€” SQL injection, NoSQL injection, and command injection.

Use parameterized queries: The same rule as REST APIs β€” never interpolate user input directly into query strings. GraphQL doesn't add any protection against injection; the protection must be in your resolver's database layer.

GraphQL Security Hardening Checklist

  1. Disable introspection in production environments
  2. Implement query depth limiting (max depth 5-10 for most APIs)
  3. Add query complexity analysis β€” reject queries above a cost threshold
  4. Disable or limit query batching on authentication endpoints
  5. Enforce authorization at every resolver that returns sensitive data
  6. Use parameterized queries in all resolvers β€” no string interpolation
  7. Apply rate limiting at the operation level, not just HTTP request level
  8. Enable query persisting (allow only pre-approved queries in production)
  9. Audit your schema for unintended field exposure via introspection testing

Test Your GraphQL API for Security Issues

AquilaX DAST tests GraphQL endpoints for introspection exposure, authorization bypasses, injection vulnerabilities, and query abuse vectors.

Start Free Scan