Why GraphQL Has a Unique Security Profile
REST APIs have multiple endpoints, each with explicit inputs and outputs. GraphQL exposes a single endpoint where clients specify exactly what data they want β including relationships, nested objects, and computed fields. This flexibility creates security challenges that traditional REST scanning tools don't cover.
The schema-driven nature means a complete map of your data model is potentially just one introspection query away. The flexible query system means attackers can craft expensive queries that exhaust server resources. Authorization mistakes at the resolver level affect all queries that touch that data.
Introspection Exposure
GraphQL's introspection system allows any client to query the entire schema β all types, fields, mutations, and their descriptions. In development, this powers IDE autocompletion. In production, it hands attackers a complete map of your data model.
# Standard introspection query β reveals entire schema
{
__schema {
types {
name
fields {
name
type { name }
description
}
}
}
}Disable introspection in production. Most GraphQL servers support this with a single config option. For development, require authentication to run introspection queries.
import strawberry from strawberry.extensions import DisableIntrospection schema = strawberry.Schema( query=Query, extensions=[ DisableIntrospection # disable in production ] )
Batching Attacks and Brute Force
GraphQL supports query batching β sending multiple operations in a single HTTP request. An attacker can use this to bypass rate limiting. Instead of sending 1000 login requests (blocked at 5/minute), they send 10 requests each containing 100 batched login mutations.
[
{ "query": "mutation { login(user:\"admin\", pass:\"pass001\") { token } }" },
{ "query": "mutation { login(user:\"admin\", pass:\"pass002\") { token } }" },
// ... 998 more attempts in one HTTP request
]Mitigations: disable query batching, or limit the number of operations per batch. Apply rate limiting at the operation level, not just the HTTP request level.
Deeply Nested Query DoS
GraphQL allows arbitrarily deep queries across relationships. An attacker can craft a query that traverses circular references millions of levels deep, consuming exponential server resources:
# author.posts.author.posts.author.posts... β exponential cost
{ author(id: "1") {
posts { author { posts { author { posts {
author { posts { author { posts { title } } } }
} } } }
}
}Implement query depth limiting (typically max depth 5-10) and query complexity analysis. Libraries like graphql-depth-limit (JS) or equivalent for your stack handle this.
Authorization Flaws in Resolvers
In REST, authorization is typically enforced at the endpoint level. In GraphQL, authorization must be enforced at the resolver level β every field that returns sensitive data needs an authorization check. Missing one resolver means that data is accessible via any query that includes that field.
Common mistake: Checking authorization at the query root but not in nested resolvers. A user who can query { post(id: 1) { title } } might be able to also retrieve { post(id: 1) { author { email phone } } } if the author resolver doesn't check permissions.
Injection in Resolvers
GraphQL arguments are passed to resolvers, which then query databases or call other services. If resolver code constructs raw queries from GraphQL arguments, all the same injection vulnerabilities apply β SQL injection, NoSQL injection, and command injection.
Use parameterized queries: The same rule as REST APIs β never interpolate user input directly into query strings. GraphQL doesn't add any protection against injection; the protection must be in your resolver's database layer.
GraphQL Security Hardening Checklist
- Disable introspection in production environments
- Implement query depth limiting (max depth 5-10 for most APIs)
- Add query complexity analysis β reject queries above a cost threshold
- Disable or limit query batching on authentication endpoints
- Enforce authorization at every resolver that returns sensitive data
- Use parameterized queries in all resolvers β no string interpolation
- Apply rate limiting at the operation level, not just HTTP request level
- Enable query persisting (allow only pre-approved queries in production)
- Audit your schema for unintended field exposure via introspection testing
Test Your GraphQL API for Security Issues
AquilaX DAST tests GraphQL endpoints for introspection exposure, authorization bypasses, injection vulnerabilities, and query abuse vectors.
Start Free Scan