GraphQL Security: Common Vulnerabilities and How to Test Them
GraphQL gives clients flexible access to data through a single endpoint. That flexibility changes the security picture: instead of many endpoints with fixed responses, you have one endpoint where clients decide what to ask for.
Introspection
GraphQL can describe its own schema via introspection queries. In production, this hands attackers a complete map of types, fields and mutations, including ones the frontend never uses.
Defence: disable introspection in production for private APIs. Note that schema details can still leak through error suggestions, such as "Did you mean adminUsers?", so disable field suggestions too.
Authorisation at the resolver level
The most serious GraphQL bugs are authorisation failures. Because clients can traverse relationships, a check on the top-level query is not enough:
query {
me {
organization {
members { email }
invoices { total }
}
}
}
If organization.invoices does not check the caller's permissions, any member, or a user reaching the organisation through another path, may see data they should not.
Defence: enforce authorisation in every resolver, or in a data access layer that all resolvers use, never only at the entry point.
Node and ID lookups
Relay-style APIs often expose node(id:), which fetches any object by global ID. This is a classic BOLA risk.
Defence: apply the same per-object authorisation in the node resolver as anywhere else.
Denial of service through query complexity
Clients can write deeply nested or expensive queries:
query { user { friends { friends { friends { friends { name } } } } } }
Defences:
- Maximum query depth.
- Query complexity or cost analysis.
- Pagination limits on lists.
- Timeouts and resource limits.
- Persisted queries, allowing only known queries in production.
Batching and aliasing attacks
GraphQL lets clients send many operations in one request, using arrays or aliases:
mutation {
a1: login(email: "[email protected]", password: "pass1") { token }
a2: login(email: "[email protected]", password: "pass2") { token }
}
This can bypass rate limits that count HTTP requests, enabling brute force of passwords or one-time codes.
Defence: rate limit by operation, not by request, and limit aliases and batch sizes.
Injection
Arguments in GraphQL are still user input. Resolvers that build SQL, NoSQL or shell commands from arguments are vulnerable to injection just like REST handlers.
CSRF on GraphQL
If your GraphQL endpoint accepts cookie authentication and allows GET requests or application/x-www-form-urlencoded bodies, CSRF may be possible. Require application/json and use CSRF protections.
GraphQL security checklist
| Control | Purpose |
|---|---|
| Introspection and suggestions off in production | Reduce schema exposure |
| Resolver-level authorisation | Prevent data leaks through relationships |
| Depth and complexity limits | Prevent DoS |
| Operation-level rate limits | Stop batching abuse |
| Input validation | Prevent injection |
| JSON-only requests | Reduce CSRF |
Testing GraphQL
SecurityInfinity's API agent detects GraphQL endpoints, extracts the schema where possible, tests resolvers for authorisation with multiple identities, and checks depth, batching and injection. Start free.
Frequently asked questions
Is GraphQL less secure than REST?
Not inherently, but it shifts risks. Authorisation and resource consumption need more deliberate design.
Does disabling introspection secure a GraphQL API?
No. It reduces information exposure but attackers can still discover fields through other means. Authorisation is what protects data.
What are persisted queries?
A list of pre-approved queries stored on the server. Clients send a query ID instead of arbitrary query text, which blocks unexpected and expensive queries.