40%
of enterprise APIs now expose a GraphQL endpoint (Akamai State of the Internet 2025)
83%
of GraphQL APIs in the wild have introspection enabled in production (SANS 2025 API Security Survey)
6x
higher average severity of GraphQL findings vs REST API findings in bug bounty programs (HackerOne 2025)

GraphQL is not a drop-in replacement for REST from a security perspective. The query language's flexibility introduces attack classes that simply do not exist in REST APIs -- and most security teams apply their REST API testing playbooks to GraphQL endpoints without adaptation, missing the majority of findings.

The core risks stem from three architectural properties: GraphQL typically exposes a single endpoint, the schema is often self-documenting via introspection, and query execution is controlled by the client rather than the server. Each of these properties creates exploitable conditions when not explicitly defended against.

This guide covers GraphQL-specific vulnerability classes, a testing methodology for each, tooling, and the remediation controls that address each class at the schema and resolver level.

GraphQL vs. REST: Why the Attack Surface Is Different

REST APIs distribute their attack surface across multiple endpoints, each with a defined response shape. A pentester or scanner can enumerate the API by crawling documented or discovered routes.

GraphQL concentrates everything at a single endpoint (typically /graphql or /api/graphql). The client sends a query document specifying exactly what data it wants, and the server resolves that against a typed schema. This creates several security-relevant differences:

Client-controlled query depth and breadth: In REST, the server decides what data a GET /users/{id} response includes. In GraphQL, the client specifies the full selection set -- including nested relationships. Without server-side limits, a client can craft a query that traverses the full object graph, triggering thousands of database queries.

Schema self-documentation via introspection: GraphQL servers can respond to introspection queries that return the full schema: every type, field, query, mutation, and their relationships. This is useful for development tooling but gives attackers a complete map of the API before they test a single endpoint.

Resolver-level authorization: REST endpoints typically have a single authorization check at the route level. GraphQL resolvers fire individually for each field in the selection set. A developer who authorizes the parent user query but forgets to add authorization to the user.paymentMethods field creates a BOLA that is invisible to route-level security controls.

Batching and multiplexing: GraphQL supports multiple operations in a single request (query batching) and subscriptions (persistent connections for real-time data). Both create rate-limiting bypass and DoS opportunities that REST testing does not exercise.

Introspection Abuse: Mapping the Schema Before You Test

Introspection is the first thing to test against any GraphQL endpoint. If enabled, it reveals the complete schema.

Basic introspection query

Send this to the GraphQL endpoint:

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

If the server returns a full type list, introspection is enabled. Immediately export the schema and analyze it for:

  • Admin, internal, or debug query types (look for admin, internal, debug, _, or dev prefixes)
  • Mutation operations that modify sensitive data (password reset, role assignment, credit balance adjustment)
  • Types that reference PII or financial data
  • Fields that are exposed but likely not intended for public consumers (created by dev schemas merged into production)

Tooling for introspection enumeration

  • GraphQL Voyager: Visualizes the full schema as an interactive graph. Excellent for identifying object relationships and finding traversal paths to sensitive data.
  • Clairvoyance: Wordlist-based schema inference tool for when introspection is disabled but the endpoint still processes queries. It uses field name guessing and error messages to reconstruct the schema without introspection.
  • InQL (Burp Suite Extension): Parses the introspection response and auto-generates all queries and mutations as Burp requests for manual testing.
  • graphql-cop: Automated security audit tool that checks introspection, depth limits, field suggestions, and other misconfigurations in a single scan.

When introspection is disabled: Many production GraphQL servers disable introspection but still return field suggestion errors (Did you mean 'email'?). Clairvoyance exploits these suggestions to reconstruct the schema. Test for this explicitly -- disabling introspection is not equivalent to hiding the schema.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

Query Depth and Complexity Attacks (GraphQL DoS)

Without server-side limits, a single GraphQL request can trigger unbounded computation. Two primary attack patterns:

Deeply nested queries (circular reference DoS)

If a User type has a friends field that returns [User], and each User has a friends field, the following query resolves recursively:

{
  user(id: 1) {
    friends {
      friends {
        friends {
          friends {
            friends { id email }
          }
        }
      }
    }
  }
}

At 10 levels of nesting with 50 friends per user, this triggers 50^10 database queries from a single HTTP request. This is a textbook application-layer DoS that bypasses network-level rate limiting.

Field duplication (alias-based batching)

GraphQL allows field aliasing. A client can request the same expensive resolver 1,000 times in a single query:

{
  a1: expensiveField { id }
  a2: expensiveField { id }
  a3: expensiveField { id }
  ...repeat 997 more times
}

Each alias fires a separate resolver invocation, multiplying the computation from a single request.

Testing for depth/complexity limits

Increase query depth by one level per request and observe when (if ever) the server returns an error. Most servers without limits will process all depths until they time out or OOM.

Remediation

  • Set maximum query depth (5-10 levels is reasonable for most applications)
  • Implement query complexity scoring -- assign costs to fields and reject queries exceeding a budget
  • Use query timeout enforcement server-side
  • Enable persisted queries (only pre-approved query documents can execute) for production APIs
  • Libraries: graphql-depth-limit (Node.js), graphene-django query depth limiting (Python), graphql-query-complexity (Node.js)

Authorization Failures: BOLA and BFLA in GraphQL

Broken Object Level Authorization (BOLA) and Broken Function Level Authorization (BFLA) manifest differently in GraphQL than REST, and are harder to test systematically because authorization is implemented at the resolver level rather than the route level.

BOLA in GraphQL: field-level access control gaps

A developer adds authorization to the user query resolver (you can only query your own user object) but forgets to add authorization to the user.orders field resolver. An authenticated attacker queries:

{
  user(id: 9999) {
    orders {
      id total items { productId quantity }
    }
  }
}

The user query may fail (ID not yours), but if the orders resolver fetches data without checking ownership, it returns the victim's order history.

Testing approach

  1. Authenticate as User A; capture the ID of another user (User B)
  2. For every type in the schema that has an ID field, test whether you can retrieve User B's data by substituting their ID
  3. Test nested fields independently -- the parent may be authorized but child resolvers may not be
  4. Test mutations with User B's IDs (update User B's email address, delete User B's resource)

BFLA in GraphQL: admin mutations accessible to regular users

Common patterns:

  • setUserRole(userId, role) mutation accessible to non-admin users
  • deleteUser, banUser, or refundOrder mutations without role checks
  • Internal admin queries exposed in the same schema as user queries (check for admin-prefixed types)

Remediation

  • Implement authorization at the resolver level using a middleware pattern (graphql-shield for Node.js, Strawberry or graphene permissions for Python)
  • Test authorization for every resolver, not just top-level query/mutation fields
  • Consider using separate schemas for admin vs. public APIs to enforce physical isolation
  • Add automated authorization tests to your CI/CD pipeline that verify each field requires authentication

Batching Attacks and Rate Limiting Bypass

GraphQL's batching feature allows multiple operation documents to be sent in a single HTTP request as a JSON array. This is useful for client performance optimization but trivially bypasses rate limits applied at the HTTP request level.

Credential stuffing via query batching

If a login mutation exists and the server accepts batched queries:

[
  {"query": "mutation { login(email: \"victim@example.com\", password: \"password1\") { token } }"},
  {"query": "mutation { login(email: \"victim@example.com\", password: \"password2\") { token } }"},
  {"query": "mutation { login(email: \"victim@example.com\", password: \"password3\") { token } }"}
]

A rate limit of 10 requests per minute becomes 10,000 password attempts per minute if each request contains 1,000 operations.

Brute-forcing 2FA codes via field aliases

Similar to the alias-based DoS above, aliases can be used to submit 1,000 different OTP guesses in a single request:

{
  a0: verifyOtp(code: "000000") { success }
  a1: verifyOtp(code: "000001") { success }
  ...999 more
}

Testing for batching

Send a batched array request with 10-100 identical mutations and observe whether all execute or the server rejects the batch.

Remediation

  • Disable query batching if the application does not require it
  • If batching is required, limit batch size (maximum 5-10 operations per request)
  • Implement rate limiting at the operation level, not just the HTTP request level
  • Apply CAPTCHA or step-up authentication to sensitive mutations regardless of batching

Information Disclosure via Verbose Error Messages

GraphQL servers commonly return detailed error messages that reveal internal implementation details: stack traces, database query text, resolver function names, and schema structure hints.

What to look for

  • Stack traces in error responses that reveal file paths, function names, and library versions
  • Database error messages: Sequelize.UniqueConstraintError, column 'internal_flag' does not exist, PSQL error codes
  • Field suggestion errors: Cannot query field 'adminNotes' on type 'User'. Did you mean 'notes'? -- reveals field names even when introspection is disabled
  • Resolver debugging output left in production from console.log statements

Testing approach

Send intentionally malformed queries and observe the error detail level. Send queries for fields that may not exist and check if suggestions are returned. Inspect error responses for stack traces.

Remediation

  • Disable field suggestions in production (allowedLegacyNames: false, or server-specific configuration)
  • Return generic error messages to clients; log full detail server-side
  • Disable stack traces in production error responses
  • Use a GraphQL error masking library to sanitize error responses before they reach the client

Security Testing Toolchain for GraphQL

Reconnaissance and schema mapping

  • Clairvoyance: Schema inference without introspection via wordlist-based field guessing
  • GraphQL Voyager: Interactive schema visualization once introspection is enabled or schema is obtained
  • InQL (Burp Suite extension): Auto-generates query and mutation templates from introspection response; integrates with Burp Scanner

Automated scanning

  • graphql-cop: Security audit CLI tool; checks introspection status, depth limits, field suggestions, alias overloading, query batching, and POST method enforcement in a single run
  • Escape: Commercial SaaS GraphQL security testing platform; generates and executes test cases automatically
  • Nuclei: Has GraphQL-specific templates for common misconfigurations; run with -t graphql

Manual testing support

  • Altair / Insomnia: GraphQL clients with schema exploration and request history; useful for crafting test cases
  • Burp Suite Pro: Intercept, modify, and replay GraphQL requests; the InQL extension adds schema-aware testing
  • OWASP GraphQL Cheat Sheet: Reference for authorization testing patterns, input validation requirements, and secure schema design

Integration into CI/CD

  • Run graphql-cop against staging environments in your pipeline; fail the build on critical findings
  • Use graphql-inspector to detect schema breaking changes and new fields that may lack authorization
  • Add automated BOLA tests (substituting user IDs across operations) as integration tests alongside your GraphQL schema

The bottom line

GraphQL security testing is not REST API testing with a different URL. Introspection abuse, unbounded query depth, resolver-level BOLA, batching-based rate limit bypass, and verbose error disclosure are all GraphQL-specific attack classes that require adapted methodology and tooling. Start with graphql-cop against your production endpoint to find the quick wins, then work through resolver-level authorization testing systematically using InQL-generated templates in Burp Suite.

Frequently asked questions

Is GraphQL introspection always a vulnerability?

Introspection is not inherently a vulnerability, but enabling it in production for a public-facing API significantly aids attackers. Introspection reveals the complete schema -- every type, field, query, and mutation -- giving attackers a map of the entire API before they send a single business logic request. Best practice: disable introspection in production, enable it only in development and staging environments, and control access to it in internal APIs via authentication.

How is BOLA different in GraphQL compared to REST?

In REST, authorization is typically enforced at the route level, making it easier to audit (one authorization check per endpoint). In GraphQL, each field has its own resolver, and authorization must be implemented at every resolver independently. A developer who secures the parent query resolver but forgets the child field resolver creates a BOLA that is invisible to route-level scans. Testing must exercise nested fields individually, not just top-level queries.

Can Burp Suite test GraphQL APIs effectively?

Yes, with the InQL extension. InQL parses the GraphQL schema (via introspection or a provided schema file) and generates individual Burp requests for every query and mutation, making it easy to test each operation in Burp Scanner and Repeater. Native Burp Suite Pro also intercepts and modifies GraphQL requests in flight. For automated scanning without Burp, graphql-cop covers the most common misconfiguration classes in a CLI tool.

What is a query batching attack?

GraphQL servers that accept batched requests (multiple operations in a single JSON array) allow an attacker to bypass HTTP-level rate limiting. A rate limit of 10 requests per minute becomes 10,000 operation attempts per minute if each request contains 1,000 operations. This is particularly dangerous for authentication mutations (login, OTP verification, password reset) where brute force protection relies on request-level rate limiting.

How do I implement authorization in GraphQL without missing fields?

Use a middleware layer that enforces authorization rules on every resolver via a declarative policy definition. In Node.js, graphql-shield lets you define rules per type and field in a single authorization map, making it easy to audit coverage. In Python, Strawberry has a permissions system. The key discipline: default to deny (require authorization on every field), and explicitly grant access rather than adding authorization only to fields you remember are sensitive.

What is the difference between GraphQL depth limits and complexity limits?

Depth limits restrict how many levels of nested object traversal a query can include (for example, user.friends.friends.friends would be depth 3). Complexity limits assign a cost score to each field and reject queries whose total cost exceeds a budget. Depth limits are simpler to implement but miss certain attack patterns (many fields at one level with no nesting). Complexity limits are more precise but require scoring each field. Using both together provides the strongest DoS protection.

Should GraphQL APIs be tested differently than REST APIs in a penetration test?

Yes. REST and GraphQL share common vulnerability classes (injection, authentication failures, insecure direct object references) but GraphQL has unique attack surfaces that REST testing misses: introspection enumeration, query depth/complexity DoS, alias-based batching for rate limit bypass, and resolver-level BOLA. A REST API test checklist applied to a GraphQL endpoint will miss 40-60% of GraphQL-specific findings. Use InQL, graphql-cop, and Clairvoyance as GraphQL-specific tooling alongside standard application security testing.

Sources & references

  1. OWASP GraphQL Cheat Sheet
  2. OWASP API Security Top 10
  3. MITRE ATT&CK: Exploit Public-Facing Application
  4. SANS 2025 API Security Survey
  5. HackerOne Hacker-Powered Security Report
  6. NIST SP 800-95: Guide to Secure Web Services

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, weekly, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals weekly.

Free Brief

The Mythos Brief is free.

AI that finds 27-year-old zero-days. What it means for your security program.

Joins Decryption Digest. Unsubscribe anytime.

Daily Briefing

Get briefings like this every morning

Actionable threat intelligence for working practitioners. Free. No spam. Trusted by 50,000+ SOC analysts, CISOs, and security engineers.

Unsubscribe anytime.

Mythos Brief

Anthropic's AI finds zero-days your scanners miss.