REST APIs have a predictable attack surface. Each endpoint does one thing, accepts defined parameters, and returns a fixed response structure. Security testers know where to look: test each endpoint for injection, test each parameter for manipulation, test each resource for access control.
GraphQL changes everything. A single endpoint accepts any query the client constructs. Clients define what data they receive, how deeply nested the response is, and which fields are included. The schema describes every type, field, relationship, and mutation the API supports. And if introspection is enabled (it is by default), the attacker can download that entire schema with one query, gaining a complete map of your data model and every operation available.
GraphQL's flexibility is its security challenge. The same features that make it powerful for developers (client-defined queries, deeply nested relationships, batch operations, real-time subscriptions) create attack vectors that don't exist in REST APIs and that traditional API security testing tools don't adequately cover.
This guide covers why GraphQL introduces unique security challenges, the specific vulnerabilities testers find in GraphQL APIs, attack techniques for each vulnerability, the testing methodology, tools used in professional GraphQL security assessments, a secure GraphQL checklist, and how API penetration testing validates GraphQL security.
Why GraphQL Security Is Different from REST
Single Endpoint, Unlimited Queries
REST APIs expose many endpoints, each with a defined function. GraphQL exposes one endpoint that accepts any valid query against the schema. Traditional API scanners that enumerate endpoints and test each one individually don't work against a single-endpoint architecture. GraphQL security testing requires understanding the schema and crafting queries that test every type, field, and relationship.
Client Controls the Response
In REST, the server decides what data to return. In GraphQL, the client's query defines which fields are included in the response. This means the server must enforce authorization on every individual field, not just every endpoint. A REST endpoint either returns the user object or doesn't. A GraphQL query can request user { name email } (safe) or user { name email ssn internalNotes adminFlags } (dangerous) in the same request.
Schema as Attack Blueprint
GraphQL introspection returns the complete API schema: every type, every field, every mutation, every argument, every relationship. In REST terms, this is equivalent to handing the attacker your complete API documentation, every database table name, and every available operation. Introspection is enabled by default in most GraphQL frameworks.
Nested Query Complexity
GraphQL allows nested queries that traverse relationships to arbitrary depth. A query like users { posts { comments { author { posts { comments { author { ... } } } } } } } creates exponential database load. REST APIs don't have this vector because each endpoint returns a fixed response structure.
Common GraphQL Vulnerabilities
Vulnerability 1: Introspection Enabled in Production
What it is: GraphQL introspection allows anyone to query the complete schema, revealing every type, field, mutation, subscription, argument, and relationship the API supports.
Attack technique:
# Standard introspection query
{
__schema {
types {
name
fields {
name
type { name kind ofType { name } }
args { name type { name } }
}
}
mutationType { fields { name args { name type { name } } } }
subscriptionType { fields { name } }
}
}What the attacker learns: Complete data model (every entity and its fields). Every available mutation (write operations). Hidden fields not exposed in the frontend. Relationships between entities enabling IDOR testing. Administrative mutations the frontend never calls.
Real-world impact: An attacker discovers a deleteUser mutation, an adminSettings type with fields like internalApiKey and billingOverride, and a userPrivate type containing ssn, bankAccount, and internalScore. None of these appear in the frontend. All are queryable.
How to fix: Disable introspection in production. Every major GraphQL framework provides this option. Apollo Server: introspection: false in production config. Express-GraphQL: introspection: false. Hasura: environment variable. Some organizations leave introspection enabled for authenticated admin users only.
Vulnerability 2: Batching Attacks (Brute Force via Query Batching)
What it is: GraphQL supports sending multiple queries in a single HTTP request (query batching) or multiple aliased operations in one query. Attackers use this to bypass rate limiting and brute-force authentication, enumerate resources, or amplify attacks.
Attack technique:
# Alias-based batching: 100 login attempts in one request
{
a0: login(username: "admin", password: "password1") { token }
a1: login(username: "admin", password: "password2") { token }
a2: login(username: "admin", password: "password3") { token }
# ... 97 more aliases
a99: login(username: "admin", password: "password100") { token }
}
// Array-based batching: multiple queries in one HTTP request
[
{"query": "{ user(id: 1) { email } }"},
{"query": "{ user(id: 2) { email } }"},
{"query": "{ user(id: 3) { email } }"}
]Real-world impact: Traditional rate limiting counts HTTP requests. One HTTP request containing 100 aliased login attempts counts as one request. The attacker brute-forces credentials at 100x the rate your rate limiter allows. Same technique applies to OTP verification, resource enumeration, and any operation benefiting from volume.
How to fix: Implement query-level rate limiting (count operations, not HTTP requests). Limit maximum aliases per query. Limit array batch size (or disable array batching). Apply rate limiting to specific sensitive operations (login, password reset, OTP verification) at the resolver level, not just the HTTP layer.
Vulnerability 3: Deeply Nested Queries (DoS via Query Complexity)
What it is: GraphQL relationships allow queries of arbitrary depth. Attackers craft deeply nested queries that create exponential database load, consuming server resources and causing denial of service.
Attack technique:
# Exponential query: each level multiplies database queries
{
users(first: 100) {
posts(first: 100) {
comments(first: 100) {
author {
posts(first: 100) {
comments(first: 100) {
author {
posts(first: 100) {
title
}
}
}
}
}
}
}
}
}
# This single query could generate 100^6 = 1 trillion database operationsReal-world impact: A single carefully crafted query crashes the GraphQL server, exhausts database connections, or generates cloud infrastructure costs exceeding thousands of dollars. No authentication may be required if the GraphQL endpoint is public.
How to fix: Implement query depth limiting (maximum nesting depth, typically 7 to 10 levels). Implement query complexity analysis (assign cost to each field, reject queries exceeding a complexity budget). Set query timeout limits. Implement pagination limits on list fields. Libraries: graphql-depth-limit, graphql-query-complexity, graphql-validation-complexity.
Vulnerability 4: Authorization Bypass (Field-Level vs Object-Level)
What it is: GraphQL requires authorization at multiple levels: operation level (can this user call this mutation?), object level (can this user access this specific record?), and field level (can this user see this specific field on this record?). Most implementations check only one level, leaving the others unprotected.
# Object-level bypass: accessing another user's data
{
user(id: "other-user-id") {
email
phone
address
paymentMethods { last4 expiry }
}
}
# Field-level bypass: requesting fields the UI never shows
{
me {
name
email
# Fields the frontend doesn't request but the schema exposes
internalRole
accountBalance
apiKey
adminNotes
}
}
# Mutation authorization bypass: calling admin mutations as regular user
mutation {
updateUserRole(userId: "target-id", role: ADMIN) {
id role
}
}Real-world impact: Regular users access other users' personal data through object-level bypass (IDOR). Regular users see sensitive fields through field-level bypass. Regular users perform administrative operations through mutation authorization bypass. This is the GraphQL equivalent of broken access control, consistently the highest-severity finding category.
How to fix: Implement authorization at every level: operation (can this role call this mutation?), object (does this user own this record?), and field (can this role see this field?). Use authorization middleware that runs before every resolver. Never rely on the frontend to restrict which fields are queried. Test authorization on every field and mutation, not just the ones the frontend uses.
Vulnerability 5: IDOR Through GraphQL Mutations
What it is: GraphQL mutations that modify data using client-supplied IDs without verifying the requesting user owns the target resource.
Attack technique:
# Modify another user's profile
mutation {
updateProfile(userId: "victim-id", input: {
email: "attacker@evil.com"
phone: "+1234567890"
}) {
id email phone
}
}
# Delete another user's resource
mutation {
deleteDocument(documentId: "victim-document-id") {
success
}
}
# Transfer from another user's account
mutation {
transferFunds(fromAccount: "victim-account", toAccount: "attacker-account", amount: 10000) {
transactionId
}
}Real-world impact: Account takeover by changing another user's email. Data destruction by deleting another user's resources. Financial fraud through unauthorized transfers. Every mutation accepting resource IDs is a potential IDOR target.
How to fix: Every mutation must verify the requesting user is authorized to modify the target resource. Never trust client-supplied IDs without ownership validation. Implement resolver-level authorization checking resource ownership before executing any modification.
Vulnerability 6: Subscription Abuse
What it is: GraphQL subscriptions provide real-time data through WebSocket connections. Poorly implemented subscriptions can leak data, enable resource exhaustion, or provide persistent unauthorized access.
Attack technique:
# Subscribe to all user events (not just your own)
subscription {
userUpdated {
id name email lastLogin ipAddress
}
}
# Subscribe to all orders (data leak)
subscription {
newOrder {
orderId customer { name email address }
items { product quantity price }
paymentMethod { type last4 }
}
}Real-world impact: Attacker receives real-time feed of all user activity, order data, or system events. Subscription connections persist, providing ongoing data access even after the initial authorization check. Thousands of subscription connections exhaust server resources.
How to fix: Authenticate subscription connections at establishment and validate periodically. Authorize subscription data at the event level (only deliver events the subscriber is authorized to see). Limit concurrent subscriptions per user. Implement subscription-specific rate limiting. Timeout idle subscription connections.
Vulnerability 7: Information Disclosure Through Error Messages
What it is: GraphQL error responses revealing internal implementation details: database table names, query structures, stack traces, internal field names, and resolver implementation details.
Attack technique: Send malformed queries, invalid types, or deliberately incorrect arguments. Examine error responses for internal details.
# Trigger detailed error revealing database structure
{ user(id: "' OR 1=1 --") { name } }
# Error: "PostgreSQL error: relation 'users_table' column 'internal_id'..."
# Probe for hidden fields
{ user(id: 1) { __secretField } }
# Error: "Cannot query field '__secretField' on type 'User'.
# Did you mean 'secretNotes' or 'secretKey'?"Real-world impact: Error suggestions reveal hidden field names. Database errors reveal table structures and query patterns. Stack traces reveal technology stack and internal architecture.
How to fix: Return generic error messages in production. Log detailed errors server-side only. Disable GraphQL field suggestion in production (Apollo: includeStacktraceInErrorResponses: false). Implement custom error formatting that strips internal details.
GraphQL Security Testing Methodology
Phase 1: Schema Discovery
If introspection is enabled: Download the complete schema using introspection queries. Map every type, field, mutation, subscription, and argument.
If introspection is disabled: Use schema reconstruction tools (Clairvoyance) that brute-force field names against the API. Analyze client-side JavaScript for GraphQL queries revealing partial schema. Intercept application traffic to capture queries the frontend sends.
Output: Complete or reconstructed schema documenting the API's full attack surface.
Phase 2: Authorization Testing
For every query and mutation in the schema, test authorization at three levels.
Object-level: Can User A access User B's records by changing IDs in queries and mutations?
Field-level: Can users request fields the frontend doesn't use but the schema exposes? Are sensitive fields (internal IDs, admin flags, financial data) accessible to unauthorized roles?
Mutation-level: Can standard users call administrative mutations? Can unauthenticated users call authenticated mutations?
This is the most critical testing phase. GraphQL authorization bypass is the equivalent of BOLA in REST APIs, the most common and highest-impact finding.
Phase 3: Injection Testing
Test all query arguments, mutation inputs, and filter parameters for injection vulnerabilities.
SQL injection through GraphQL arguments. NoSQL injection for MongoDB-backed GraphQL. SSRF through URL arguments in mutations. Cross-site scripting through stored GraphQL data rendered in frontends.
Phase 4: Abuse Testing
Test for abuse vectors unique to GraphQL architecture.
Batching abuse: Alias-based and array-based batching for brute-force amplification. Query complexity: Deeply nested queries for resource exhaustion. Subscription abuse: Unauthorized data access through subscriptions. Pagination bypass: Requesting all records by manipulating first/last/offset arguments.
Phase 5: Configuration Testing
Introspection: Enabled in production? Error messages: Verbose errors revealing internals? CORS: Overly permissive allowing cross-origin GraphQL queries? Rate limiting: Applied at query level or only HTTP level? Transport: HTTPS enforced? WebSocket security for subscriptions?
GraphQL Security Testing Tools
InQL (Burp Suite Extension)
The primary tool for GraphQL security testing within Burp Suite. Fetches and analyzes GraphQL schemas. Generates queries for every type and mutation. Integrates with Burp's scanner and repeater for manual testing. Essential for any GraphQL security assessment.
GraphQL Voyager
Visual schema explorer that renders the GraphQL schema as an interactive graph showing types, fields, and relationships. Useful during reconnaissance to understand the data model and identify high-value targets (types containing sensitive data, mutations with dangerous operations).
BatchQL
Tool specifically for testing GraphQL batching vulnerabilities. Automates alias-based and array-based batching attacks. Tests rate limiting bypass through query batching. Measures server response to increasing batch sizes.
Clairvoyance
Schema reconstruction tool for APIs with introspection disabled. Brute-forces field and type names against the GraphQL endpoint. Recovers partial or complete schemas from APIs that have disabled introspection but still respond to valid queries.
graphql-cop
GraphQL security audit tool checking for common misconfigurations: introspection enabled, field suggestions enabled, batching allowed, excessive query depth permitted, and debug mode active.
Altair GraphQL Client
Feature-rich GraphQL client for manual testing. Supports queries, mutations, subscriptions, file uploads, and environment variables. Useful for crafting and replaying complex test queries.
Standard API Testing Tools
Burp Suite Professional remains essential for intercepting, modifying, and replaying GraphQL requests. Postman supports GraphQL natively. These tools test the HTTP layer wrapping GraphQL (authentication, headers, CORS, transport security).
Secure GraphQL Checklist
Schema and Introspection
- Introspection disabled in production
- Field suggestions disabled in production error messages
- Schema does not expose sensitive internal types or fields
- Deprecated fields removed (not just marked deprecated)
- Schema documentation does not reveal security-sensitive implementation details
Authentication
- All queries and mutations require authentication (except explicitly public ones)
- Authentication validated on every request (not just initial connection)
- WebSocket connections for subscriptions authenticated at establishment
- JWT/token validation enforced with proper algorithm and expiration checks
- Session management follows secure coding standards
Authorization
- Object-level authorization on every query returning user-specific data
- Field-level authorization on sensitive fields (not just type-level)
- Mutation-level authorization restricting admin operations to admin roles
- Authorization enforced in resolvers (not middleware that can be bypassed)
- IDOR tested on every mutation accepting resource IDs
Query Controls
- Maximum query depth enforced (7 to 10 levels recommended)
- Query complexity analysis with defined budget per request
- Query timeout configured
- Pagination limits enforced on list fields (no unbounded queries)
- Maximum aliases per query limited
- Array batching limited or disabled
Rate Limiting
- Rate limiting at query/operation level (not just HTTP request level)
- Sensitive operations (login, password reset, OTP) rate-limited at resolver level
- Batching-aware rate limiting counting operations, not requests
- Subscription connection limits per user
Error Handling
- Generic error messages in production (no stack traces)
- No database error details in responses
- No field name suggestions in error messages
- Errors logged server-side with full details for debugging
Transport and Configuration
- HTTPS enforced on GraphQL endpoint
- CORS restrictive (not wildcard for authenticated endpoints)
- WebSocket security for subscription transport
- Request size limits preventing oversized queries
- Content-type validation (application/json only)
GraphQL Security for Compliance
PCI DSS. GraphQL APIs processing payment data must meet Requirement 6 (secure development) and Requirement 11 (testing). Authorization bypass exposing cardholder data through GraphQL queries creates PCI DSS violations. See our PCI DSS guide.
SOC 2. GraphQL APIs handling customer data must demonstrate access control effectiveness (CC6). Field-level and object-level authorization failures in GraphQL directly impact SOC 2 compliance. See how SOC 2 pentests support compliance.
ISO 27001. Annex A.8.25 (Secure Development) and A.8.26 (Application Security Requirements) apply to GraphQL API development. See our ISO 27001 guide.
GDPR. GraphQL APIs exposing personal data of EU residents without proper authorization violate Article 32. Field-level access control failures enabling personal data exposure create GDPR compliance risk. See our GDPR guide.
For comprehensive compliance mapping, see our penetration testing compliance guide.
How AppSecure Tests GraphQL APIs
AppSecure provides comprehensive GraphQL security testing as part of API penetration testing engagements.
Complete Schema Analysis. Full schema discovery through introspection or reconstruction (Clairvoyance for introspection-disabled APIs). Every type, field, mutation, and subscription mapped and tested.
Three-Level Authorization Testing. Object-level, field-level, and mutation-level authorization tested systematically across every schema element. IDOR tested on every mutation accepting resource IDs. This is the testing that automated scanners cannot perform because it requires understanding your data ownership model.
GraphQL-Specific Attack Testing. Batching attacks for brute-force amplification. Deeply nested queries for complexity-based DoS. Subscription abuse for data leakage. Injection through all GraphQL input points.
Beyond GraphQL. GraphQL API testing integrates with web application testing, cloud testing, and network testing. Application security assessment provides full-stack coverage.
Zero False Positives. Every finding validated through exploitation with GraphQL request/response evidence. Manual testing depth discovers authorization bypasses and business logic flaws that no GraphQL scanner detects.
3-Week Delivery. 90-day remediation support. Complimentary retesting. Continuous testing and PTaaS for ongoing GraphQL security validation.
Ready for GraphQL security testing that goes beyond automated scanning?
Contact AppSecure:
Frequently Asked Questions
1. What is GraphQL security testing?
GraphQL security testing evaluates GraphQL APIs for vulnerabilities specific to GraphQL architecture: introspection exposure, authorization bypass at object and field levels, batching attacks for brute-force amplification, deeply nested queries causing denial of service, IDOR through mutations, subscription abuse, and information disclosure through error messages. Testing covers both GraphQL-specific risks and standard API security concerns (authentication, injection, configuration).
2. Why is GraphQL security different from REST API security?
GraphQL's single-endpoint architecture, client-defined queries, schema introspection, query batching, nested query depth, and subscription support create attack vectors that don't exist in REST APIs. REST authorization operates at the endpoint level. GraphQL requires authorization at object, field, and mutation levels. REST responses are server-defined. GraphQL responses are client-defined, requiring the server to validate what the client requests. Traditional API scanning tools designed for REST don't adequately cover GraphQL.
3. What are the most critical GraphQL vulnerabilities?
Authorization bypass (field-level and object-level) is the highest-severity GraphQL vulnerability, equivalent to BOLA in REST APIs. Introspection enabled in production is the most common finding, giving attackers a complete API blueprint. Batching attacks bypass rate limiting for brute-force amplification. Deeply nested queries cause denial of service. IDOR through mutations enables unauthorized data modification. Each requires GraphQL-specific testing methodology.
4. Should introspection be disabled in production?
Yes. Introspection reveals the complete API schema: every type, field, mutation, subscription, and relationship. This gives attackers a comprehensive map of your data model and every available operation. Disable introspection in production. If development teams need schema access, provide it through authenticated admin-only introspection or separate documentation rather than exposing it to all API consumers.
5. What tools are used for GraphQL security testing?
Primary tools include InQL (Burp Suite extension for schema analysis and query generation), GraphQL Voyager (visual schema exploration), BatchQL (batching vulnerability testing), Clairvoyance (schema reconstruction when introspection is disabled), graphql-cop (configuration audit), and Altair GraphQL Client (manual query crafting). Burp Suite Professional handles HTTP-layer testing. Manual expert testing discovers authorization bypass and business logic issues tools cannot detect.
6. How do batching attacks bypass rate limiting?
Traditional rate limiting counts HTTP requests. GraphQL batching sends multiple operations in a single HTTP request, either through aliases (multiple operations in one query) or array batching (multiple query objects in one request body). 100 login attempts as aliases in one request count as one HTTP request against the rate limiter. Fix by implementing operation-level rate limiting that counts individual operations, not HTTP requests.
7. What is query depth limiting and why does it matter?
Query depth limiting restricts how deeply nested GraphQL queries can be. Without limits, attackers craft queries traversing relationships to extreme depth, creating exponential database load from a single request. A query nesting 6 levels deep with 100 items per level could generate trillions of database operations. Implement maximum depth (7 to 10 levels) and query complexity analysis with cost budgets per request.
8. How do you test GraphQL authorization?
Test authorization at three levels. Object-level: change resource IDs in queries to access other users' data. Field-level: request sensitive fields the frontend doesn't use but the schema exposes. Mutation-level: call administrative mutations with standard user credentials. Test every type and mutation in the schema, not just the ones the frontend uses. This requires manual testing because automated tools don't understand data ownership.
9. Does GraphQL security testing replace REST API testing?
No. If your application uses both GraphQL and REST APIs, both require security testing. GraphQL-specific testing covers GraphQL's unique attack vectors (introspection, batching, nested queries, field-level auth). REST API testing covers REST-specific concerns (endpoint-level BOLA, HTTP method manipulation). Many applications use GraphQL for frontend queries and REST for backend integrations. Both layers need coverage.
10. How often should GraphQL APIs be tested?
Annual GraphQL security testing at minimum. After schema changes adding new types, fields, or mutations. After authorization model changes. After enabling new features (subscriptions, federation). After framework upgrades that may change default security settings. Continuous testing through PTaaS for GraphQL APIs with frequent schema evolution. Every schema change is a potential new authorization gap.

Tejas K. Dhokane is a marketing associate at AppSecure Security, driving initiatives across strategy, communication, and brand positioning. He works closely with security and engineering teams to translate technical depth into clear value propositions, build campaigns that resonate with CISOs and risk leaders, and strengthen AppSecure’s presence across digital channels. His work spans content, GTM, messaging architecture, and narrative development supporting AppSecure’s mission to bring disciplined, expert-led security testing to global enterprises.






.webp)




_%20Examples%2C%20Impact%20%26%20How%20to%20Fix%20Them.webp)


_.webp)




















%20Tools%20vs%20Penetration%20Testing.webp)












.webp)























































.webp)
