Strobesstrobes
Platform
Solutions
Resources
Customers
Company
Pricing
Book a Demo
Strobesstrobes

Strobes connects every exposure signal to autonomous action, so security teams fix what matters, prove what works, and stop chasing noise.

Book a DemoTalk to an expert
ISO 27001SOC 2CREST
  • Platform
  • Platform Overview
  • Agentic Exposure Management
  • AI Agents
  • Integrations
  • API & Developers
  • Workflows & Automation
  • Analytics & Reporting
  • Solutions
  • Exposure Assessment (EAP)
  • Attack Surface Management
  • Application Security Posture
  • Risk-Based Vulnerability Management
  • Adversarial Exposure Validation (AEV)
  • AI Pentesting
  • Pentesting as a Service
  • CTEM Framework
  • By Industry
  • Financial Institutions
  • Technology
  • Retail
  • Healthcare
  • Manufacturing
  • By Roles
  • CISOs
  • Security Directors
  • Cloud Security Leaders
  • App Sec Leaders
  • Resources
  • Quick Agentic Pentest
  • Blog
  • Customer Stories
  • eBooks
  • Datasheets
  • Videos & Demos
  • Exposure Management Academy
  • CTEM Maturity Assessment
  • Pentest Health Check
  • Security Tool ROI Calculator
  • Company
  • About Strobes
  • Meet the Team
  • Trust & Security
  • Contact Us
  • Careers
  • Become a Partner
  • Technology Partner
  • Partner Deal Registration
  • Press Release

Weekly insight for security leaders

CTEM research, agentic AI trends, and what's actually moving the needle.

© 2026 Strobes Security Inc. All rights reserved.

Privacy PolicyTerms of ServiceCookie PolicyAccessibilitySitemap
Back to Blog
GraphQL Security Testing: A Complete Guide
Application Security

GraphQL Security Testing: A Complete Guide

Likhil ChekuriDecember 26, 20247 min read

Table of Contents

  • How do you recover a GraphQL schema?
  • Disabling introspection is not a security control
  • How do you test authorization in GraphQL?
  • Aliasing and batching turn one request into a thousand
  • How do you fix what a GraphQL test finds?
  • Frequently asked questions
  • Sources and references

Authors

L
Likhil Chekuri

Share

Table of Contents

  • How do you recover a GraphQL schema?
  • Disabling introspection is not a security control
  • How do you test authorization in GraphQL?
  • Aliasing and batching turn one request into a thousand
  • How do you fix what a GraphQL test finds?
  • Frequently asked questions
  • Sources and references

Authors

L
Likhil Chekuri

Share

TL;DR
  • ✓GraphQL returns HTTP 200 even for errors, so your signal is in the JSON body, not the status code. That single fact makes most DAST scanners useless here.
  • ✓One endpoint fronts the whole data model, so testing starts with schema recovery via introspection or Clairvoyance inference.
  • ✓The same OWASP API risks apply (BOLA, BFLA, mass assignment), just expressed as queries and mutations, and nested-resolver gaps are the most common serious finding.
  • ✓Aliasing and batching pack hundreds of operations into one HTTP request, defeating per-request rate limits on login and OTP.
  • ✓Disabling introspection is not a control. Authorization must be enforced in every resolver, and cost limits must cap query depth.

Here is the fact that decides how you test GraphQL: it returns HTTP 200 even when the request failed, with the real outcome buried in an errors array next to data in the JSON body. A DAST scanner that grades by status code sees a wall of 200s and concludes everything is fine, even as a malicious query inside the body reads another tenant's records. That is why GraphQL testing is almost entirely manual or agentic, and why a tool that confirms the endpoint 'speaks GraphQL' has told you nothing useful.

This guide walks the full process: recovering the schema (even when introspection is off), testing nested-resolver authorization where the real bugs hide, abusing aliasing and batching to defeat rate limits, measuring query-cost DoS, the tooling that makes it practical, and the configuration that fixes each finding. Example queries and real output are included throughout.

Table of contents
  1. How do you recover a GraphQL schema?
  2. Disabling introspection is not a security control
  3. How do you test authorization in GraphQL?
  4. Aliasing and batching turn one request into a thousand
  5. How do you fix what a GraphQL test finds?

How do you recover a GraphQL schema?

You recover the schema first, ideally through introspection, falling back to inference when it is disabled, because the schema is the entire roadmap: every type, query, mutation, and field. Start by fingerprinting the engine with graphw00f (Apollo, Hasura, and graphql-ruby leak differently and ship different defaults), then fire an introspection query:

POST /graphql HTTP/1.1
Content-Type: application/json

{ "query": "{ __schema { mutationType { fields { name } } } }" }

--- response ---
HTTP/1.1 200 OK
{ "data": { "__schema": { "mutationType": { "fields": [
  { "name": "updateUser" },
  { "name": "deleteUser" },          # <- admin-looking mutation, never called by the UI
  { "name": "impersonate" } ]}}}}    # <- enumerate and try each with a low-priv token

That deleteUser and impersonate are immediate BFLA candidates. Load the introspection result into InQL to auto-generate fire-ready queries. If introspection is disabled, Clairvoyance rebuilds the schema from the field-suggestion text in error responses, which is why the toggle is not a security boundary (more on that next).

Strobes insight
GraphQL returns 200 even for errors, so the status code is meaningless. Read the errors array in the body, or you will miss the bug a scanner already missed for the same reason.

Disabling introspection is not a security control

Turning off introspection slows an attacker by minutes, not more, because GraphQL error messages leak the schema anyway through 'Did you mean ...' field suggestions. Clairvoyance automates the reconstruction:

$ clairvoyance -o schema.json https://api.target.com/graphql

[+] Field suggestions enabled, inferring schema...
[+] Recovered 64 types, 38 queries, 19 mutations    # <- introspection was OFF
[+] Wrote schema.json

On a recent assessment of a fintech GraphQL API, the client had proudly disabled introspection and listed it as a mitigating control in their last report. Clairvoyance rebuilt about 90% of the schema from suggestion messages in under ten minutes, including a refundPayment mutation the UI never exposed. The lesson is blunt: introspection toggles are operational hygiene, not a boundary. The boundary is authorization in every resolver, and that is where the real testing happens.

How do you test authorization in GraphQL?

You test authorization per field and per object, because GraphQL enforces access inside resolvers that are easy to forget. BOLA and BFLA are as common here as in REST; they just appear inside a query argument or a mutation. A flat BOLA looks like passing another user's ID:

POST /graphql
{ "query": "{ user(id: 1052) { email phone ssn } }" }

--- response ---
HTTP/1.1 200 OK
{ "data": { "user": {
    "email": "victim@x.com",
    "ssn": "..." }}}                  # <- your token, victim's data = BOLA

The more common and more serious finding is the nested-resolver gap: a query you are allowed to run exposes a related object you are not, because the developer protected the top-level resolver and forgot the one it stitches in:

POST /graphql
{ "query": "{ order(id: 9) { total
    customer { email ssn } } }" }    # order is checked; customer is NOT

--- response ---
HTTP/1.1 200 OK
{ "data": { "order": { "total": 240,
    "customer": { "email": "other@x.com" }}}}   # <- nested authZ gap

That nested gap is the single most common serious GraphQL finding, and the reason is structural: most frameworks make it trivial to add an authorization directive on a query but offer no default protection on the resolvers that hydrate related types. A developer guards order, ships, and never realizes order.customer runs its own unguarded resolver. What good looks like is authorization enforced at the type or field level, so the check travels with the data regardless of which query reaches it. For BFLA, take every mutation InQL recovered (the deleteUser and refundPayment from earlier) and call each with a standard token. If the resolver runs, the function-level check was never written. Watch for the false positive here too: a mutation that returns { "errors": [{ "message": "Not authorized" }] } with a 200 status is enforced, not bypassed, so always read the body rather than trusting the code. The patterns mirror the API pentest checklist, expressed as queries rather than REST routes, and overlap heavily with REST API penetration testing.

GraphQL security testing checklist
Schema recovery
  • ✓Fingerprint engine with graphw00f
  • ✓Run introspection query
  • ✓Recover disabled schema with Clairvoyance
  • ✓Parse and build queries with InQL
Authorization
  • ✓BOLA: pass other users' IDs to queries
  • ✓Nested-object authorization gaps (the big one)
  • ✓BFLA: invoke every recovered mutation with a low-priv token
GraphQL-specific
  • ✓Aliasing to bypass login rate limits
  • ✓Batching abuse on login/OTP
  • ✓Deeply nested query DoS / cost limits
  • ✓Mass assignment via mutation inputs
  • ✓Introspection or field suggestions in production

Aliasing and batching turn one request into a thousand

The native GraphQL attack surface is query structure: depth, batching, and aliasing, which enable denial of service and rate-limit bypass that REST simply does not expose. The highest-value test is aliasing to defeat a login throttle. The server counts one HTTP request; you pack hundreds of login attempts into it:

POST /graphql
{ "query": "{
  a: login(user:\"admin\", pw:\"123456\"){ token }
  b: login(user:\"admin\", pw:\"password\"){ token }
  c: login(user:\"admin\", pw:\"qwerty\"){ token }
  ... 500 more aliases ...
}" }

--- response ---
HTTP/1.1 200 OK     # <- one request, 500 guesses, zero 429s: rate limit defeated

To prove impact, do not just note the structure: send 200 guesses for a known username in one aliased request and show that none tripped the 429 a normal client hits after five. The other structural attacks:

  • Deeply nested queries (DoS): if types reference each other, nest { author { posts { author { posts ... } } } }. Measure timing as you add levels. If five levels take 200ms and eight take 12 seconds, you have demonstrated a cost-limit gap without taking the server down.
  • Batching: many servers accept an array of operations in one request, again sidestepping per-request limits on login or OTP.
  • Mass assignment via mutations (BOPLA): pass input fields the client never shows, like { updateUser(input:{id:1, role:"ADMIN"}) { id } }. See mass assignment attacks.

How do you fix what a GraphQL test finds?

The findings table below is the shape of a real GraphQL report excerpt, with each gap tied to evidence and a config-level fix. The fixes map directly to the attacks above, and most are configuration rather than code rewrites:

  • Nested and flat BOLA: enforce object and field-level authorization in every resolver, not just the top-level query. This is the fix for the single most common GraphQL finding.
  • Query-cost DoS: apply a maxDepth limit, query cost analysis, and per-operation timeouts. Pick the depth from the timing curve you measured.
  • Alias and batch abuse: cap or disable batching on sensitive operations, reject requests with excessive aliases, and rate-limit per operation and per identity, not per HTTP request.
  • Mass assignment: allow-list input fields on mutations so role can never be client-set.
  • Introspection: disable it and field suggestions in production, knowing it only slows recovery.

Treat GraphQL as a first-class part of your API security program. For continuous coverage across REST and GraphQL together, see how agentic pentesting runs these checks on every deploy.

Findings table (report excerpt)
FindingSeverity (CVSS)EvidenceFix
Nested-resolver BOLA on order.customerCritical (9.1)order(id:9) leaked another tenant's customerAuthorize every resolver, not just top-level
BFLA on deleteUser mutationCritical (9.1)Standard token ran deleteUser, HTTP 200Function-level check in the mutation resolver
Alias-based login rate-limit bypassHigh (8.1)500 guesses in one request, zero 429sRate-limit per operation + cap aliases
Query-depth DoSHigh (7.5)8-level query took 12s vs 200ms at 5maxDepth + cost analysis + timeout
Schema recovered despite disabled introspectionMedium (5.3)Clairvoyance rebuilt 90% from suggestionsDisable field suggestions in production

Frequently asked questions

Is GraphQL less secure than REST?
GraphQL is not inherently less secure, but it shifts and adds risk. The same authorization and injection bugs from REST apply, plus GraphQL-specific issues like query-depth DoS, aliasing, and batching that bypass naive rate limits. Its single-endpoint, client-specified-query model means authorization must be enforced per resolver, which is easy to get wrong.
What is a GraphQL introspection attack?
Introspection is a built-in feature that lets a client query the full schema, including every type, query, and mutation. Left enabled in production, it hands an attacker a complete map of the API. Even when disabled, tools like Clairvoyance reconstruct much of the schema from error-message field suggestions, so it is not a real boundary.
How do you test for BOLA in GraphQL?
Find a query or mutation that takes an object ID as an argument, then supply an ID belonging to another user while authenticated as yourself. If the response returns that user's data, it is a BOLA. Crucially, also test nested objects: a permitted query may expose a related object whose resolver does not check authorization, which is the most common serious finding.
What is a GraphQL batching attack?
Many GraphQL servers accept multiple operations in one request, either as an array of queries or via aliases. An attacker uses this to run hundreds of operations such as login attempts in a single HTTP request, bypassing per-request rate limits and enabling brute force or enumeration at scale.
What tools are used for GraphQL security testing?
graphw00f fingerprints the GraphQL engine, InQL (a Burp extension) parses introspection and generates queries, Clairvoyance recovers schemas when introspection is disabled, and GraphQL Voyager visualizes the schema. Burp Suite or mitmproxy handle interception and replay of the queries you build by hand.
How do you prevent GraphQL denial-of-service attacks?
Apply query-depth limits and cost analysis so deeply nested queries cannot explode the resolver tree, set per-operation timeouts, cap or disable batching and aliasing on sensitive operations, and rate-limit per operation rather than per HTTP request. Pick the maxDepth value from the timing curve you measured during testing.

Sources and references

  • OWASP GraphQL Cheat Sheet
  • OWASP API Security Top 10 (2023)
  • Clairvoyance
L
Likhil Chekuri
Application Security Engineer, Strobes
Likhil Chekuri is an AppSec engineer at Strobes who has run hundreds of web, mobile, and cloud penetration tests for regulated industries.
Tags
API SecurityGraphQLApplication Security

Stop chasing vulnerabilities Start reducing exposure

See how Strobes AI agents validate and fix your most critical exposures automatically.

Book a Demo
Continue Reading

Related Posts

Vulnerability validation: why most of your scanner backlog is noise - Strobes
Exposure ValidationApplication Security

Vulnerability Validation: Why Most of Your Scanner Backlog Is Noise

Vulnerability validation proves which scanner findings are real, reachable, and exploitable. Why manual triage fails and how agentic validation scales.

Jun 9, 202619 min
How to pentest single-page applications - React, Angular and Vue SPA security testing guide
Penetration TestingApplication Security

How to Pentest Single-Page Applications (React, Angular, Vue)

Learn how to pentest React, Angular, and Vue SPAs. Covers DOM XSS, client-side routing bypass, JS bundle secrets, and why traditional DAST scanners fail.

Jun 4, 202623 min
Bug bounty vs pentesting vs AI pentesting comparison featured image
Penetration TestingApplication Security

Bug Bounty vs. Pentesting vs. AI Pentesting: Which Model Fits Your AppSec Program?

Bug bounty vs pentesting vs AI pentesting: compare costs, coverage, compliance, and when to use each model. Build a layered AppSec testing strategy.

Jun 4, 202621 min