GraphQL Security Fundamentals
GraphQL is a query language for APIs that gives clients precise control over what data they request. Unlike REST, where endpoints return fixed data shapes, GraphQL clients specify exactly which fields they need in a query. This flexibility introduces security challenges that do not exist in REST APIs.
GraphQL APIs share authentication and authorization requirements with REST APIs but add GraphQL-specific attack vectors:
- Query depth attacks: Deeply nested queries that create exponential database load
- Query complexity attacks: Wide queries requesting thousands of fields per request
- Introspection leaks: The schema is self-describing — attackers can enumerate all types, fields, and relationships
- Batching abuse: Multiple operations in a single request bypass per-request rate limiting
- Field-level authorization failures: REST access control is per-endpoint; GraphQL access control must be per-field in each resolver
GraphQL is not inherently more or less secure than REST. Security depends entirely on implementation. Many GraphQL APIs ship with default configurations that are insecure in production.
Query Depth and Complexity Attacks
GraphQL's recursive schema allows deeply nested queries that create exponential database queries. A simple 6-level nested query on a social graph can trigger millions of database calls:
query {
user(id: 1) {
friends {
friends {
friends {
friends {
friends {
name
}
}
}
}
}
}
}
Depth limiting
Vulnerable (no depth limit):
const server = new ApolloServer({ typeDefs, resolvers });
// No depth limit — attacker can nest queries arbitrarily deep
Secure (depth limited):
import depthLimit from 'graphql-depth-limit';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(5)]
});
Complexity limiting
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('Query cost:', cost),
formatErrorMessage: (cost) =>
`Query too complex (${cost}). Maximum allowed: 1000.`
})
]
});Introspection Leaks in Production
GraphQL introspection allows any client to query the complete schema — all types, fields, relationships, mutations, and subscriptions. In development, this powers tools like GraphQL Playground and Apollo Explorer. In production, it gives attackers a complete map of your API surface.
An attacker who discovers a GraphQL endpoint can enumerate the schema with a single query:
query {
__schema {
types {
name
fields {
name
type { name }
}
}
}
}
The response reveals every data type, every field name, and every relationship — including fields that should not be accessible.
Disable introspection in production (Apollo Server):
const server = new ApolloServer({
typeDefs,
resolvers,
introspection: process.env.NODE_ENV !== 'production'
});Batching Attacks and Rate Limiting
GraphQL supports sending multiple operations in a single HTTP request (batching). A rate limiter that counts HTTP requests will see one request while the server executes 100 operations — effectively bypassing per-request rate limits.
// Single HTTP request, 100 brute-force attempts
[
{ "query": "mutation { login(email: \"admin@example.com\", password: \"pass1\") { token } }" },
{ "query": "mutation { login(email: \"admin@example.com\", password: \"pass2\") { token } }" }
// ... 98 more
]
Disable batching unless needed
const server = new ApolloServer({
typeDefs,
resolvers,
allowBatchedHttpRequests: false // Default false in Apollo Server v4+
});
Rate limit by operation count
app.use('/graphql', (req, res, next) => {
const body = req.body;
const operationCount = Array.isArray(body) ? body.length : 1;
if (operationCount > 10) {
return res.status(429).json({ error: 'Too many operations in batch' });
}
next();
});How CodeSlick Detects GraphQL Issues
CodeSlick's API security checks cover GraphQL-specific patterns in JavaScript and TypeScript:
- Missing depth limiting: Apollo Server setup without
depthLimit()validation rule - Introspection in production:
introspection: truewithout an environment guard - Missing authentication middleware: GraphQL route handlers without authentication context validation
- Batching without limits: GraphQL handlers that accept array bodies without operation count validation
All findings include OWASP API Top 10 mapping (API4: Unrestricted Resource Consumption for depth/complexity, API5: Broken Function Level Authorization for introspection) and AI-powered fix suggestions for Apollo Server and graphql-js setups.
Scan your GraphQL API code for authentication gaps, missing authorization, and input validation issues across 5 languages.