GraphQL Nested Fragment and Alias DoS: Proving Cost Without Melting Prod
GraphQL lets clients ask for exactly what they want. Attackers ask for too much on purpose. Nested fragments, circular fragment spreads, and alias fan-outs can turn one HTTP request into thousands of resolver calls.
Read the program policy before you touch this. Many ban DoS. When allowed, keep proofs small and measurable.
Cheap signals before heavy payloads
Start with depth. A selection set ten levels deep on a recursive type (friends { friends { ... }) often trips a depth limit—or does not. If it does not, stop at a depth that shows quadratic growth in time or response size, not a production outage.
Aliases multiply work without deepening the tree:
{
a1: user(id: "1") { email }
a2: user(id: "1") { email }
...
}
If one hundred aliases take roughly one hundred times the resolver time of one, you have a cost amplification story. Capture timings from Burp, not from a stopwatch guess.
Fragments hide complexity. A fragment that spreads itself, or a chain of fragments that re-select the same expensive connection, is how "small" documents explode after validation. Some servers collapse duplicates; others do not. Learn which camp you are in with a tiny lab payload first.
Batch arrays—[{query:..},{query:..}—are another multiplier when the gateway authorizes once and executes many. Count that as a separate finding if each element re-runs heavy resolvers.
What "good enough" proof looks like
You want:
- Request document under a size the gateway accepts
- Clear CPU or latency delta vs a baseline query of similar byte length
- No need for a second attacker machine
- A stop condition you respect (threshold latency, not "until it dies")
Canary metrics help: response time, extensions.tracing if enabled, or server-side error that admits complexity limits were exceeded. If the host returns 200 with a multi-megabyte JSON of repeated nodes, truncate the screenshot and measure bytes.
I've had triage accept a finding with 50 aliases and a 15x latency jump. I've had them reject a "theoretical" circular fragment with no timing. Bring numbers. Graph the alias count vs milliseconds if you can; one chart beats three paragraphs of speculation.
Defenses you should name
Recommend query depth limits, alias limits, complexity scoring (per-field weights), persisted queries for browsers, and timeouts around resolvers that hit the DB. Mention that disabling introspection does nothing here—the attack uses known fields.
Persisted-query-only modes shrink this class for first-party apps. Third-party API customers still need server-side cost controls.
Stay boring in production
Never run the payload that flattened staging against the shared prod cluster at full scale. Prefer dedicated accounts, off-peak hours if required, and stop once impact is clear. If a WAF starts blocking you, that is evidence too—document the threshold.
When the program forbids DoS entirely, still note missing complexity limits as an observation in a broader GraphQL hardening report—or skip filing and keep the note private. Do not "accidentally" prove availability impact on a shared tenant.
Complexity bugs are real. Prove cost, cite limits that are missing, and leave the nuclear option in the lab.