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:
- Check introspection exposure in production.
- Test depth and complexity limits with progressively expensive queries.
- Test query batching at scale.
- Test alias-based amplification on expensive resolvers.
- Walk field-level authorization across roles.
- Walk object-level authorization across tenants.
- Test mutation mass-assignment and authorization.
- 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.
Related articles
Preparing for your first pentest? Download the SMB Pentest Readiness Checklist →