
A definitive, hands-on 2026 security audit guide for GraphQL APIs: covering schema discovery, introspection analysis, authorization auditing (BOLA/BFLA), query complexity DoS prevention, and
Learn how to install Kali Linux dual boot alongside Windows 10/11. Complete step-by-step guide with partitioning, GRUB setup, troubleshooting, and safety warnings to prevent data loss.
13 min read
What breaks on an OAuth or OIDC login: redirect matching, PKCE, state, and the Host header. What I check, and what I turn on. No exploit lab.
12 min read
Basic understanding of HTTP, REST APIs, JSON, and Python/JavaScript.
Burp Suite, InQL extension, Python 3, Node.js, GraphQLmap
Master GraphQL schema discovery, introspection analysis, authorization auditing (BOLA/BFLA), query complexity DoS prevention, and secure schema hardening.
GraphQL has rapidly shifted modern application architectures from standard REST endpoints to unified, single-endpoint query engines (/graphql). While GraphQL offers immense frontend flexibility, it fundamentally alters the API security boundary. Traditional REST security controls—such as URI-path-based Web Application Firewalls (WAFs) and route-level middleware—are ineffective against complex, nested GraphQL queries.
This comprehensive field guide provides security engineers, bug bounty hunters, and developers with a structured, step-by-step methodology for auditing GraphQL APIs. You will learn:
Before auditing a GraphQL interface, security auditors must understand how GraphQL processes inbound requests at the AST (Abstract Syntax Tree) level.
Client (HTTP POST) ──> [/graphql Endpoint] ──> GraphQL Parser / Lexer
│
▼
Abstract Syntax Tree (AST)
│
▼
Validation Phase (Type System)
│
▼
Execution & Resolver Engine
│
▼
Backend DB / Microservices
| Security Dimension | REST API Architecture | GraphQL API Architecture |
|---|---|---|
| Endpoint Topology | Multiple distinct URLs (/api/v1/users, /api/v1/orders) | Single entrypoint (/graphql or /api/v1/query) |
| HTTP Methods | GET, POST, PUT, PATCH, DELETE | Almost exclusively POST (occasionally GET for cached queries) |
| Data Specification | Server dictates schema structure per route | Client dictates requested shape and field hierarchy |
| Authorization Layer | Applied at HTTP route middleware | Applied inside field-level resolver functions |
| WAF Inspection | Inspects URL path parameters and HTTP headers | Must parse deep JSON POST request bodies and AST nodes |
When auditing an application where GraphQL endpoints are not publicly documented, search common location patterns using automated wordlists or HTTP proxy traffic analysis:
/graphql
/api/graphql
/v1/graphql
/v2/graphql
/query
/api/v1/query
/graphiql
/playground
/console
/explorer
GraphQL features a built-in introspection system allowing clients to query the schema structure (__schema and __type).
{
"query": "query IntrospectionQuery { __schema { queryType { name } mutationType { name } subscriptionType { name } types { ...FullType } directives { name description locations args { ...InputValue } } } } fragment FullType on __Type { kind name description fields(includeDeprecated: true) { name description args { ...InputValue } type { ...TypeRef } isDeprecated deprecationReason } inputFields { ...InputValue } interfaces { ...TypeRef } enumValues(includeDeprecated: true) { name description isDeprecated deprecationReason } possibleTypes { ...TypeRef } } fragment InputValue on __InputValue { name description type { ...TypeRef } defaultValue } fragment TypeRef on __Type { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name ofType { kind name } } } } } } } }"
}
If introspection is disabled, modern GraphQL engines (Apollo Server, GraphQL-js) often return field suggestions when a query contains typos:
// Request
{
"query": "{ usr { id } }"
}
// Response (Leaking Schema Types!)
{
"errors": [
{
"message": "Cannot query field \"usr\" on type \"Query\". Did you mean \"user\", \"users\", or \"authUser\"?",
"locations": [{ "line": 1, "column": 3 }]
}
]
}
Tools like Clairvoyance leverage field suggestion responses to systematically reconstruct complete GraphQL schemas even when standard introspection is turned off.
BOLA occurs when a user can query objects belonging to other users simply by changing an identifier in a field argument.
// Vulnerable: Fetches user strictly by ID provided in query argument
const resolvers = {
Query: {
userProfile: async (_, { userId }, context) => {
// Missing context.user owner check!
return await Database.getUserById(userId);
}
}
};
query AuditBOLA {
userProfile(userId: 1002) {
id
email
creditCardNumber
roles
}
}
// Secure: Validates authenticated context against requested resource owner
const resolvers = {
Query: {
userProfile: async (_, { userId }, context) => {
if (!context.user) {
throw new Error("UNAUTHENTICATED");
}
if (context.user.id !== userId && context.user.role !== 'ADMIN') {
throw new Error("UNAUTHORIZED_ACCESS_DENIED");
}
return await Database.getUserById(userId);
}
}
};
Because types in GraphQL can reference each other cyclically (e.g., User -> Posts -> Author -> Posts), an attacker can craft deeply nested queries that exhaust server memory and CPU resources.
query CircularResourceExhaustion {
user(id: 1) {
posts {
author {
posts {
author {
posts {
author {
name
email
}
}
}
}
}
}
}
}
GraphQL specs permit sending array payloads to execute multiple operations in a single HTTP POST request.
[
{"query": "{ user(id: 1) { email } }"},
{"query": "{ user(id: 2) { email } }"},
{"query": "{ user(id: 3) { email } }"},
...
{"query": "{ user(id: 1000) { email } }"}
]
To prevent circular queries and batching abuse in Node.js/Apollo, implement query depth limits and query cost estimation.
import express from 'express';
import { ApolloServer } from '@apollo/server';
import depthLimit from 'graphql-depth-limit';
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
// 1. Enforce max query depth of 5 levels
depthLimit(5),
// 2. Enforce max query complexity score of 1000 points
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('Query complexity cost:', cost),
}),
],
});
GraphQL resolvers act as standard application code and must sanitize all inputs passed down to databases or OS commands.
query AuditSQLi {
searchProducts(category: "Electronics' OR '1'='1--") {
id
name
price
}
}
// Secure Resolver using Parameterized DB Driver
const resolvers = {
Query: {
searchProducts: async (_, { category }, context) => {
// Safe: Uses parameter bindings
const query = 'SELECT id, name, price FROM products WHERE category = $1';
const res = await db.query(query, [category]);
return res.rows;
}
}
};
__schema and __type queries unless public API specs are required.EmailAddress, DateTime) rather than generic String.GraphQL delivers exceptional developer ergonomics, but security must be built directly into the resolver and AST validation pipelines. By combining schema introspection discovery, field-level access control checks, and query depth limiting, organizations can secure their GraphQL APIs against modern attack vectors.
Share this tutorial
Master Cross-Site WebSocket Hijacking (CSWSH) in 2026. Explore HTTP/1.1 upgrade mechanics, Origin header bypasses, token leakage via query params, weaponized HTML/JS exploit PoCs, Python audi
22 min read
Sign in to leave a comment.