API Security

GraphQL Security: Common Vulnerabilities and Testing Approaches

GraphQL has its own attack surface on top of OWASP API Top 10 — introspection, batching, alias confusion, depth/complexity, and field-level authorization. How testers find each.

Author
CyberGuards Security Research Team
Published
Updated
Read
12 min read

Why GraphQL is different (without being scarier)

GraphQL was not designed to be less secure than REST. Most security issues in GraphQL endpoints map cleanly to OWASP API Top 10 categories. What is different is the execution model: a single endpoint, a flexible query shape, resolvers that can return arbitrary nested structures, and aliasing that lets a client repeat a call multiple times in a single request. That model rewards careful authorization design and punishes naive resource limits.

Introspection — the schema is part of the attack surface

GraphQL introspection lets a client query the schema itself. Useful for tooling, IDE autocomplete, and partner onboarding. Risky in production because it gives an attacker a full map of operations, types, and field names — accelerating targeted attacks.

Tester check. Submit a standard introspection query in production. If the schema returns, that is an exposure decision your team should have made consciously.

Remediation. Disable introspection in production for externally-exposed APIs. For internal-only or partner-only APIs, gate introspection behind authentication.

Depth and complexity DoS

GraphQL queries can be arbitrarily deep. A query that nests user.friends.friends.friends... ten levels deep can multiply the work of a single request by orders of magnitude. Without limits, a single client can cause server-side load disproportionate to network cost.

Tester check. Submit progressively deeper queries until the server either rejects them or accepts them and times out. Either tells you something.

Remediation. Static query analysis with depth and complexity limits. Cost-per-field annotations. Query allow-listing via persisted queries for known clients.

Batching attacks

GraphQL servers commonly accept query batches — multiple operations in one HTTP request. Useful for client efficiency. Dangerous if rate limits are applied per-request rather than per-operation.

Tester check. Submit a batch with a hundred login attempts in one HTTP request. If the rate limiter only sees one HTTP request, the limit is broken for batched workloads.

Remediation. Apply rate limits per operation, not per HTTP request. Cap batch size at the gateway.

Alias-based amplification

GraphQL aliases let a client query the same field multiple times in one request: a: expensiveCall, b: expensiveCall, c: expensiveCall, .... A single request can invoke an expensive resolver hundreds of times. Without complexity-aware limits, this is the GraphQL equivalent of a billion-laughs attack.

Tester check. Submit a request with twenty aliased calls to a single expensive resolver and observe rate limiting.

Remediation. Cost-per-field complexity analysis that counts aliases. Cap aliases per request at the gateway.

Field-level authorization

Object-level authorization (BOLA) is shared with REST; GraphQL adds field-level authorization as a distinct concern. A query asks for an object the user is allowed to see, but includes a sensitive field they are not. If the resolver returns the field anyway, the protection at the object level was insufficient.

Tester check. Walk every protected field across every role. Diff response shapes; if a sensitive field is returned for a role that should not see it, that is a finding.

Remediation. Authorization at the data-access layer, scoped per field where roles differ. Schema directives can declare requirements but should not be the only enforcement.

Mutation safety

Mutations are GraphQL's write operations. Two patterns to verify:

  • Mass assignment. A mutation accepts an input object; the input includes fields the user should not control. The server processes them anyway. Same pattern as REST mass assignment but easier to miss because the input shape is schema-defined.
  • Authorization on input identifiers. A mutation that updates an object by ID needs to verify the caller is authorized to update that object — same as BOLA on reads, but writes are usually higher-impact.

Remediation. Explicit input allow-lists per mutation, per role. Authorization checked at the data-access layer for every mutation that takes an identifier.

SSRF and unsafe consumption in resolvers

Resolvers often perform server-side fetches — to databases, internal services, third-party APIs. Two risks:

  • SSRF. A resolver accepts a URL from the client and fetches it. Same SSRF pattern as REST.
  • Unsafe consumption. A resolver fetches third-party data and returns it without validation. A compromised third party can poison your responses.

Remediation. Allow-list URLs at the resolver layer. Validate third-party responses with the same rigor as user input.

A practical eight-step test workflow

If you have access to the GraphQL endpoint and at least two test accounts in different tenants:

  1. Check introspection exposure in production.
  2. Test depth and complexity limits with progressively expensive queries.
  3. Test query batching at scale.
  4. Test alias-based amplification on expensive resolvers.
  5. Walk field-level authorization across roles.
  6. Walk object-level authorization across tenants.
  7. Test mutation mass-assignment and authorization.
  8. Identify SSRF and unsafe-consumption surfaces in resolvers.

The default mistake we see most often is GraphQL servers that delegate all authorization to the data-access layer but apply rate limits at the HTTP request layer. The combination — careful authorization plus naive rate limits — leaves the API safe for normal clients and wide open to batched and aliased abuse.

Preparing for your first pentest? Download the SMB Pentest Readiness Checklist →

FAQ

GraphQL security — common questions

Is GraphQL inherently less secure than REST?

No. GraphQL has a different attack surface than REST, not a weaker one. Issues like broken object-level authorization apply to both. GraphQL adds its own categories — introspection, batching, depth/complexity, alias confusion — that need explicit testing.

Should we disable GraphQL introspection in production?

Yes for most production deployments, especially externally-exposed APIs. Introspection exposes the full schema, which makes targeted attacks easier. Internal-only or partner APIs may permit introspection if the consumer needs it.

What is the biggest GraphQL-specific risk?

In our engagements, depth and complexity DoS plus field-level authorization gaps are the two most common high-impact issues. Both are GraphQL-specific in flavor — REST APIs have analogues, but the GraphQL execution model makes them more direct.

Do persisted queries fix the security problems?

They mitigate query-shape abuse (depth, complexity, alias confusion) by restricting to an allowed list. They do not fix authorization issues — if a persisted query returns data it should not, persisting the query does not change that.

Should authentication and authorization happen at resolver level or schema level?

Authorization should happen at the data-access layer, called from resolvers. Schema-level directives can declare requirements but should not be the only enforcement. Authentication is typically context-level (extracted from the request) and made available to all resolvers.

Want a credible answer when a customer, auditor, or your board asks how secure you are?

A quick scoping call with the senior tester who would run your engagement. No slides, no pitch — we look at what you have, tell you what we would test first, and give you a fixed scope, price, and date.