Why GraphQL Security Is Different
GraphQL fundamentally changes the API paradigm compared to REST. Instead of multiple endpoints with fixed data structures, GraphQL provides a single endpoint where clients specify exactly what data they need through queries. This flexibility is powerful for developers but creates a unique set of security challenges that many organizations fail to address.
The core issue is that GraphQL shifts control from the server to the client. In a REST API, the server determines what data each endpoint returns. In GraphQL, the client constructs a query that can request any combination of fields and relationships defined in the schema. This client-driven data retrieval model means that traditional API security approaches, which rely on controlling what each endpoint exposes, are insufficient.
Here in San Francisco's technology ecosystem, where CyberGuards conducts the majority of our API security assessments, GraphQL adoption has surged over the past three years. We have seen it implemented in everything from early-stage startup MVPs to enterprise platforms serving millions of users. And in our testing, GraphQL APIs consistently present a higher density of security findings than their REST counterparts, not because GraphQL is inherently less secure, but because the security model requires a fundamentally different approach that many development teams have not yet adopted.
Vulnerability #1: Introspection Exposure
GraphQL includes a built-in introspection system that allows clients to query the schema itself, discovering all available types, fields, queries, mutations, and their relationships. While introspection is invaluable during development, leaving it enabled in production hands attackers a complete map of your API surface.
The Risk
An attacker who can run an introspection query obtains the entire API schema, including types and fields that may not be exposed in the frontend application. This reveals internal data models, administrative mutations, deprecated but still functional endpoints, and the relationships between different data types. It is the equivalent of handing an attacker your API documentation, including the parts you never intended to make public.
Testing Approach
Send a standard introspection query to the GraphQL endpoint:
{
__schema {
types {
name
fields {
name
type {
name
kind
}
}
}
queryType { name }
mutationType { name }
}
} If the server returns the full schema, introspection is enabled and should be disabled in production. Also test with field suggestions by sending a query with a slightly misspelled field name. Many GraphQL implementations will suggest the correct field name even when introspection is disabled, leaking schema information through error messages.
Remediation
- Disable introspection in production environments
- Disable field suggestions in error messages
- Implement schema allow-listing if introspection must remain partially available
- Consider using persisted queries to limit the queries clients can execute
Vulnerability #2: Query Depth and Complexity Attacks (Nested Query DoS)
GraphQL's type system allows deeply nested relationships. An attacker can exploit this by crafting queries with extreme nesting depth or complexity, forcing the server to perform exponentially expensive database operations that lead to denial of service.
The Risk
Consider a schema where a User has posts, each Post has comments, each Comment has an author (User), and each User again has posts. An attacker can construct a query that nests these relationships dozens of levels deep, creating a query that requires the server to fetch millions of records. A single malicious query can consume all available database connections, memory, and CPU, bringing the entire application down.
query MaliciousNested {
users {
posts {
comments {
author {
posts {
comments {
author {
posts {
# ...continues nesting
}
}
}
}
}
}
}
}
} Testing Approach
Construct queries with progressively increasing nesting depth and measure server response times and resource consumption. Test with both deeply nested queries and queries that request many fields at the same level (breadth attacks). Monitor the server for signs of resource exhaustion including increased response times, error responses, and service degradation for other users.
Remediation
- Query depth limiting: Set a maximum nesting depth (typically 5-10 levels depending on schema complexity)
- Query complexity analysis: Assign cost values to each field and reject queries that exceed a total complexity threshold
- Timeout enforcement: Set aggressive server-side timeouts for query execution
- Rate limiting: Implement per-client rate limiting based on query complexity, not just request count
- Pagination enforcement: Require pagination arguments for any field that returns a list
Vulnerability #3: Batching Attacks
Many GraphQL implementations support query batching, allowing clients to send multiple queries in a single HTTP request. While batching is intended to improve performance, it can be weaponized for several types of attacks.
Brute Force via Batching
An attacker can bypass rate limiting by including thousands of authentication attempts in a single batched request. If rate limiting is applied per HTTP request rather than per GraphQL operation, a single request can attempt thousands of passwords or OTP codes simultaneously.
[
{ "query": "mutation { login(email: "[email protected]", password: "password1") { token } }" },
{ "query": "mutation { login(email: "[email protected]", password: "password2") { token } }" },
{ "query": "mutation { login(email: "[email protected]", password: "password3") { token } }" },
// ...thousands more
] Alias-Based Batching
Even without array-based batching, GraphQL aliases allow multiple copies of the same query to be executed in a single request:
query AliasBatch {
a1: login(email: "[email protected]", password: "password1") { token }
a2: login(email: "[email protected]", password: "password2") { token }
a3: login(email: "[email protected]", password: "password3") { token }
} Testing Approach
Send batched requests with increasing numbers of operations and observe whether rate limiting, account lockout, or other security controls are applied per operation or per HTTP request. Test both array-based batching and alias-based batching. Specifically test authentication mutations and any operations that should be rate-limited.
Remediation
- Apply rate limiting per GraphQL operation, not per HTTP request
- Limit the maximum number of operations per batched request
- Limit the maximum number of aliases per query
- Implement operation-level cost analysis that accounts for batching
Vulnerability #4: Authorization Bypass
Authorization in GraphQL is uniquely challenging because the client determines what data to request. Unlike REST APIs where each endpoint can have its own authorization logic, GraphQL must enforce authorization at the field, type, and resolver level. This is where many implementations fail.
Common Authorization Failures
Direct Object Access via Query Manipulation: Many GraphQL APIs allow clients to query any object by ID without verifying ownership. If a user can query their own profile with user(id: "123"), they may be able to query any user by changing the ID parameter.
Field-Level Authorization Gaps: Even when type-level authorization is implemented, individual sensitive fields within a type may lack their own access controls. A user querying their own profile may be able to include fields like socialSecurityNumber or internalNotes that should only be visible to administrators.
Mutation Authorization Inconsistency: Query authorization is often implemented more carefully than mutation authorization. Testers frequently find that while read access is properly restricted, a lower-privileged user can execute administrative mutations such as deleteUser or updateRole because mutation resolvers lack their own authorization checks.
Testing Approach
- Map all available queries and mutations through introspection or schema analysis
- Authenticate as a low-privileged user and systematically attempt to access resources belonging to other users
- Test every mutation with a low-privileged user to identify authorization gaps
- Query sensitive fields on types that should be restricted based on the requester's role
- Test relationship traversal: can a user access restricted data by querying through an allowed relationship?
Vulnerability #5: Injection Attacks
GraphQL does not prevent injection attacks. While GraphQL's type system provides some input validation, it does not sanitize values against injection. Any GraphQL argument that is passed to a backend data store, external API, or operating system command is a potential injection vector.
SQL Injection Through GraphQL
If GraphQL resolvers construct SQL queries using argument values without proper parameterization, SQL injection is possible. This is particularly common in custom filter and search functionality where client-supplied values are interpolated into SQL WHERE clauses.
query {
users(filter: "name = 'admin' OR '1'='1'") {
id
email
role
}
} NoSQL Injection
Applications using MongoDB or other NoSQL databases are vulnerable to NoSQL injection through GraphQL arguments. Object-type inputs that are directly passed to NoSQL query operators can allow attackers to modify query logic.
Server-Side Request Forgery (SSRF)
GraphQL mutations that accept URL parameters (for profile images, webhooks, file imports) can be vulnerable to SSRF if the server fetches the URL without validation. This can allow attackers to access internal services, cloud metadata endpoints, and other resources not intended to be publicly accessible.
Testing Approach
- Inject SQL payloads into all string-type arguments, particularly in filter and search operations
- Test object-type arguments for NoSQL operator injection
- Probe URL-type arguments for SSRF by pointing them to internal IP addresses and cloud metadata services
- Test for OS command injection in arguments that might be passed to system commands
- Test for GraphQL-specific injection by manipulating query structure through string interpolation in variables
Vulnerability #6: Information Disclosure Through Error Messages
GraphQL's detailed error responses are a double-edged sword. In development, detailed errors accelerate debugging. In production, they leak internal implementation details to attackers.
Common Information Leaks
- Stack traces: Full server-side stack traces revealing technology stack, library versions, and internal file paths
- Database errors: SQL error messages that reveal table names, column names, and query structure
- Field suggestions: "Did you mean" suggestions that reveal undocumented schema fields
- Type system errors: Validation errors that reveal expected types, enums, and argument structures
- Resolver errors: Internal service errors that reveal microservice architecture and endpoint details
Remediation
- Implement custom error formatting that strips internal details in production
- Return generic error messages to clients while logging detailed errors server-side
- Disable field suggestions in production
- Use error masking middleware to prevent leakage of internal information
Vulnerability #7: Subscription Abuse
GraphQL subscriptions maintain persistent WebSocket connections for real-time data. These connections introduce additional attack vectors that do not exist in standard request-response APIs.
- Resource exhaustion: Opening thousands of subscription connections to exhaust server resources
- Data leakage: Subscribing to events for resources the user should not have access to
- Bypassing authorization: Authorization checks may be performed at subscription creation time but not on individual events, allowing access to data that becomes restricted after the subscription is established
Tools for GraphQL Security Testing
Several tools have emerged specifically for GraphQL security testing. Here are the ones we use regularly in our engagements.
| Tool | Purpose | Key Features |
|---|---|---|
| InQL | Burp Suite extension for GraphQL | Schema visualization, query generation, batch testing |
| GraphQL Voyager | Schema visualization | Interactive relationship mapping of the schema |
| Clairvoyance | Schema discovery | Recovers schema when introspection is disabled via field suggestion abuse |
| graphw00f | Server fingerprinting | Identifies the GraphQL server implementation |
| BatchQL | Batch attack testing | Automates query batching for brute force and DoS testing |
| graphql-cop | Security auditing | Automated checks for common GraphQL misconfigurations |
Building a GraphQL Security Checklist
Based on our experience testing GraphQL APIs across dozens of Bay Area technology companies, here is a comprehensive security checklist for GraphQL implementations:
- Disable introspection in production and suppress field suggestions in error messages
- Implement query depth and complexity limits with appropriate thresholds for your schema
- Enforce per-operation rate limiting that accounts for batching and aliases
- Implement field-level authorization in resolvers, not just at the query or type level
- Use parameterized queries in all resolvers that interact with databases
- Validate and sanitize all inputs including custom scalars and enum values
- Implement pagination with maximum limits for all list-returning fields
- Configure proper error handling that does not expose internal details
- Apply authorization to subscriptions at both creation time and per-event
- Use persisted queries when possible to restrict the queries clients can execute
- Monitor and alert on anomalous queries including unusually deep, complex, or high-volume operations
- Conduct regular penetration testing specifically targeting GraphQL-unique vulnerability classes
Moving Forward
GraphQL security requires a shift in thinking from traditional REST API security. The flexibility that makes GraphQL powerful for developers also creates a broader attack surface that demands specialized testing approaches. Organizations adopting GraphQL should invest in security training for their development teams, implement the controls outlined in this article, and include GraphQL-specific testing in their penetration testing program.
At CyberGuards, our San Francisco-based API security team has deep expertise in GraphQL security testing. We use a combination of manual testing techniques and specialized tooling to identify the vulnerabilities that generic API scanners miss. Whether you are building a new GraphQL API or securing an existing one, we can help you identify and remediate the security issues that matter most.