← Blog/API Security

GraphQL Security: The Unique Vulnerabilities API Builders Miss

GraphQL's flexibility is its biggest feature — and its biggest security risk. Developers who switch from REST often carry over REST-era assumptions that leave their GraphQL APIs wide open to attacks that simply don't exist in REST.

·10 min read

GraphQL gives clients unprecedented flexibility to query exactly the data they need. That flexibility is the entire point — and it's also why GraphQL security requires a fundamentally different approach than securing a REST API. The same features that make GraphQL powerful create attack surfaces that REST never had.

This guide covers the GraphQL-specific vulnerabilities that developers commonly miss when building their first GraphQL API, and the concrete defenses for each one.

Why GraphQL Security Is Different

In a REST API, your attack surface is relatively well-defined: a list of routes, each with a specific method and expected parameters. An attacker probing a REST API must enumerate routes and test each one.

In GraphQL, a single endpoint (/graphql) handles all queries. The schema defines every possible query, mutation, and type — and by default, GraphQL is designed to tell you exactly what that schema contains. A single query can traverse multiple relationships, request any combination of fields, and retrieve far more data than any individual REST endpoint would return.

This design philosophy — powerful, self-describing, flexible — is what makes GraphQL great and what makes it dangerous when default configurations are left in place.

1. Introspection Attacks

GraphQL introspection is a built-in feature that allows clients to query the API's schema — every type, every field, every query, and every mutation — in a single request. It's essential for developer tooling and API documentation.

It's also a complete map of your API for an attacker.

With introspection enabled in production, an attacker can run:

{
  __schema {
    types {
      name
      fields {
        name
        type { name }
      }
    }
  }
}

This returns every type and field in your schema, including admin mutations, internal types, deprecated fields that still work, and relationships between objects that reveal your data model. It's the equivalent of a REST API returning a complete route listing with parameters — except it's a feature, not a bug, and it's enabled by default in virtually every GraphQL server.

Defense:

  • Disable introspection in production. Apollo Server: introspection: false. This is one line of configuration.
  • If you need introspection for internal tooling, restrict it by IP or authenticated role.
  • Consider disabling field suggestions (the "Did you mean X?" error messages) — they leak schema information even without introspection.

2. Batching Attacks

GraphQL supports query batching — sending multiple queries in a single HTTP request. This is a legitimate performance optimization for clients that need to fetch multiple resources simultaneously.

It's also a way to amplify brute force attacks by a factor of 100 while appearing as a single HTTP request to rate limiters that operate at the HTTP layer.

A classic attack: send a single batched request containing 100 login mutations with different password guesses. Your HTTP-level rate limiter sees 1 request. Your login function executes 100 times. Standard rate limiting is completely bypassed.

Defense:

  • Limit batch size at the server level — reject batches larger than a defined threshold (e.g., 10 operations).
  • Apply rate limiting at the GraphQL operation level, not just the HTTP request level.
  • Consider disabling batching entirely if your clients don't need it.
  • Implement per-field rate limiting on sensitive operations like authentication mutations.

Check your API's security posture in 60 seconds

Scantient scans your live API for exposed headers, TLS misconfiguration, and other security gaps — no code access required.

Scan Your API Free →

3. Query Depth Attacks (Nested Query DoS)

GraphQL's relational data model allows queries to traverse object relationships arbitrarily. If your schema has a User type with a friends field that returns more User types, a malicious query can nest this infinitely:

{
  user(id: "1") {
    friends {
      friends {
        friends {
          friends {
            # ... 50 more levels
          }
        }
      }
    }
  }
}

A single query like this can cause your server to execute an exponentially large number of database queries, exhausting resources and causing a denial of service. Unlike a DDoS attack, this requires only a single HTTP request.

Defense:

  • Implement query depth limiting. Libraries like graphql-depth-limit(Node.js) allow you to set a maximum depth (e.g., 10 levels) and reject queries that exceed it.
  • Set a hard limit appropriate to your schema — most legitimate client queries don't need more than 5–7 levels of nesting.
  • Log and alert on queries that hit the depth limit — they're almost always either attacks or bugs.

4. Query Complexity Attacks

Depth limiting alone isn't sufficient. A shallow but extremely wide query can be just as expensive:

{
  products(first: 10000) {
    id name description price category
    reviews(first: 10000) { id text author rating }
    variants(first: 10000) { id color size stock }
  }
}

This query is only 3 levels deep but could return millions of database rows. A depth limit of 10 would allow this query through completely.

Defense:

  • Implement query complexity analysis. Assign a cost to each field (simple fields: 1, list fields: higher costs based on expected cardinality) and reject queries that exceed a total complexity budget.
  • Libraries like graphql-query-complexity handle this calculation automatically with configurable field cost definitions.
  • Set pagination limits on all list fields — never allow first: 10000 without a cap.
  • Consider query timeouts as a backstop — reject any query that takes longer than a defined threshold to execute.

5. Authorization at the Resolver Level

REST APIs typically enforce authorization at the route level — a middleware checks whether the authenticated user can access this endpoint. In GraphQL, the same endpoint handles all queries, which means authorization must be enforced at the resolver level for each field.

This is where many GraphQL APIs have critical vulnerabilities: a developer adds authentication to the GraphQL endpoint but forgets that individual resolvers need their own authorization checks. An authenticated user who can query me { id email } shouldn't be able to query user(id: "admin") { id email role }.

Defense:

  • Implement field-level authorization in every resolver that returns sensitive data. Never assume that authenticating the request is sufficient.
  • Use a schema authorization library like graphql-shield to define authorization rules declaratively and apply them consistently.
  • Test authorization by running queries as a low-privilege user and attempting to access admin-only fields. This is the GraphQL equivalent of IDOR testing.

6. Mutation Rate Limiting and CSRF

Mutations — GraphQL's equivalent of POST/PUT/DELETE — can be vulnerable to CSRF if your API accepts simple content types or cookies for authentication. While most modern GraphQL APIs use token-based auth that naturally prevents CSRF, any API that acceptsapplication/x-www-form-urlencoded or text/plain content types may be vulnerable.

Defense:

  • Accept only application/json content type for GraphQL requests.
  • Use authorization headers (Bearer tokens) rather than cookies for API authentication.
  • If cookies are required, implement CSRF tokens on all mutation operations.

GraphQL Security Checklist

  • ✅ Introspection disabled in production
  • ✅ Field suggestions disabled (or restricted to authenticated users)
  • ✅ Batch query size limited (max 10 operations per request)
  • ✅ Query depth limit implemented (recommend max 10 levels)
  • ✅ Query complexity analysis and budget enforcement
  • ✅ Pagination limits on all list fields
  • ✅ Field-level authorization in all resolvers returning sensitive data
  • ✅ Per-operation rate limiting (not just HTTP-level)
  • ✅ Only application/json content type accepted
  • ✅ Error messages don't expose internal schema details
  • ✅ Query timeout implemented as a backstop

For a broader view of API security vulnerabilities beyond GraphQL-specific issues, the complete API security guide covers the full attack surface. And the 7 API security mistakes killing startups includes several that apply equally to GraphQL and REST.

Scan Your API Free — 60 Seconds

Scantient scans your production API externally — no code access, no SDK, no setup. Get a security score and actionable findings in under a minute.