GraphQL hands clients an unusual amount of control: they decide what data to request, how deeply to nest it, and how many operations to send at once. That flexibility is the whole appeal — and the whole security problem. The most important GraphQL vulnerabilities are not exotic; they are ordinary authorisation and resource-limiting failures amplified by a query language that lets attackers ask for exactly what they want.
Why GraphQL changes the attack surface
A REST API exposes many endpoints with fixed shapes, so security controls naturally attach to routes. GraphQL exposes a single endpoint and moves all the structure into the query, which breaks route-based assumptions:
- Endpoint-level authorisation is nearly meaningless — everything is
POST /graphql. - Rate limiting by request count is nearly meaningless — one request can contain enormous work.
- WAFs and scanners built around URLs and parameters see very little.
Security has to move to where the structure now lives: the schema and the resolvers.
Introspection exposure
GraphQL ships with a built-in documentation system: send an introspection query and the server returns the entire schema — every type, query, mutation and field, including deprecated fields and internal or admin operations the frontend never calls. For an attacker, that is a complete, machine-readable map of your API on request.
Disable introspection in production unless your API is deliberately public. But treat that as reducing reconnaissance, not as a security control: field suggestions in error messages (did you mean...) and wordlist-based schema reconstruction tooling mean a determined tester will recover much of the schema anyway. Anything you were relying on staying hidden — an internal mutation, an unreleased feature — still needs real authorisation behind it.
Query depth and complexity abuse
Because clients compose queries, an attacker can compose pathological ones. Circular relationships in the schema (a user has posts, a post has an author, an author has posts...) allow queries nested dozens of levels deep, each level multiplying the database work. A single well-crafted request can generate thousands of resolver calls and take down the API — no botnet required.
Defences that actually work:
- Depth limits on how deeply queries may nest.
- Query cost analysis that scores a query before executing it and rejects anything over budget, with pagination limits enforced on list fields.
- Timeouts at the resolver and request level as a backstop.
- Persisted queries in production, so clients can only execute pre-approved operations.
Batching and alias attacks
GraphQL lets one HTTP request carry many operations — via arrays of queries or via aliases that repeat the same field with different arguments. That gives attackers a way to smuggle thousands of attempts past controls that count HTTP requests: a single request containing ten thousand aliased login mutations turns your rate-limited login into an efficient credential-stuffing or OTP brute-force engine.
Defend by limiting batch size and alias counts, and by enforcing rate limits at the operation or resolver level — counting login attempts, not HTTP requests.
Field-level authorisation
This is where the serious data breaches live. With one endpoint, authorisation must be enforced per field and per object inside resolvers, and it is easy to miss:
- A
usertype exposesemailandroleto everyone because the object is returned whole, even though the UI only shows a display name. - An admin-only mutation exists in the schema and checks nothing because the developer assumed only the admin UI would call it.
- Node or object IDs are fetchable directly —
node(id: ...)patterns — letting users load other users' objects. This is classic BOLA with GraphQL syntax.
The fix is the same discipline as any API: a centralised authorisation layer that every resolver passes through, checks on the object level (is this record yours?) as well as the field level (may your role see this attribute?), and deny by default for new fields and mutations.
Other issues worth testing
- Injection through resolvers. GraphQL is transport, not sanitisation — arguments that flow into SQL or shell commands are as injectable as any parameter.
- CSRF. Servers that accept queries over GET, or mutations without CSRF protection on cookie-authenticated sessions.
- Verbose errors and debug modes leaking stack traces, schema details or internal paths.
How we test GraphQL APIs
On an API penetration test, GraphQL work starts with schema recovery — introspection where enabled, suggestion and wordlist techniques where not. From the schema we build an inventory of every query, mutation and reachable field, then run a two-account, two-tenant authorisation matrix across it, exactly as we would for REST. On top of that sit the GraphQL-specific abuse cases: depth and cost attacks, alias batching against authentication flows, and injection through arguments. The tooling differs from REST testing; the thinking — mapping who should access what, then proving the API disagrees — is the same, and it is not work a generic scanner can do.
FAQ
Should we disable introspection in production?
Yes, unless your API is intentionally public — it removes free reconnaissance. But do not mistake it for a security boundary: schemas can be substantially reconstructed, so every operation still needs real authorisation.
Are persisted queries a complete fix?
They are one of the strongest single controls, since arbitrary queries can no longer be composed. But the allowed operations themselves still need object- and field-level authorisation, and variables within them can still carry injection and BOLA payloads.
Do standard web scanners work on GraphQL?
Mostly no. Scanners built around crawling URLs and fuzzing parameters see one opaque endpoint. Some tools have partial GraphQL awareness, but authorisation flaws — the highest-impact category — require human-driven, multi-account testing.
Running GraphQL in production and not sure what an attacker would see? Get in touch — we will scope a test in a short call and have a fixed quote to you within one business day.
Need cybersecurity expertise?
Drop your email and we'll be in touch within one business day.