
Loading

Loading
We use strictly necessary cookies to run this site, and analytics cookies to understand how it's used. See our Privacy Policy for details.
A GitHub-reviewed security advisory, not necessarily under active exploitation the way our CISA KEV advisories are, but a real, disclosed vulnerability in an open-source dependency.
Affected packages
Vulnerable: >= 3.0.0, < 3.2.12
Patched in 3.2.12
Vulnerable: >= 3.3.0a1, < 3.3.0rc1
Patched in 3.3.0rc1
Details
The OverlappingFieldsCanBeMerged validation rule can be driven into quadratic — and
in the worst case O(n²·m²) — running time by a small, highly compressible query. A
single 364 KB document can occupy one CPU core for over five minutes during
validation, before any resolver runs. Because the rule is part of specified_rules
and therefore enabled by default, every GraphQL-core based server is affected at
default settings.
OverlappingFieldsCanBeMerged compares every pair of fields that share a response
name and recurses into their sub-selections. The memoization added for
CVE-2023-26144 (GHSA-9pv7-vfvm-6vr7) is keyed by named fragment identity, so it
does not cover this path: inline fragments are flattened into the parent selection
set before the cache is consulted, and therefore every pair is re-compared from
scratch at every nesting level.
Repeating an inline fragment that selects the same response name at two nesting levels multiplies the two effects. There was no comparison budget and no validation timeout, so the rule ran to completion regardless of cost.
The cost is incurred in the validation phase, which is what makes this hard to defend against from outside the library: query-depth limiters, complexity analyzers and cost estimators all operate on the parsed document after validation, and the malicious document is trivially shallow (depth 3, complexity 1). The payload is also one short string repeated many times, so it compresses to a few kilobytes and slips past body-size limits and WAF inspection that only see the compressed request.
import time
from graphql import build_schema, parse, validate
schema = build_schema(
"type Query { field: Node } type Node { f: Node, g: Node, x: String }"
)
def gen(n: int, m: int) -> str:
inner = " ".join(["... on Node { x }"] * m)
outer = " ".join([f"... on Node {{ f {{ {inner} }} }}"] * n)
return f"{{ field {{ {outer} }} }}"
query = gen(200, 100) # 364 KB, gzips to a few KB
document = parse(query)
start = time.perf_counter()
validate(schema, document) # returns no errors — it just burns CPU
print(f"{(time.perf_counter() - start):.0f}s")
Measured on GraphQL-core 3.2.8 / CPython 3.12 / Linux x86_64:
| n | m | query size | validation time |
|---|---|---|---|
| 20 | 20 | 7.6 KB | 0.16 s |
| 50 | 50 | 46 KB | 5.2 s |
| 100 | 100 | 182 KB | 81 s |
| 200 | 100 | 364 KB | 319 s |
Doubling n at fixed m quadruples the time, as expected for a quadratic outer loop
driving a quadratic inner walk.
Denial of service. One request pins one worker process for minutes; because CPython holds the GIL for this pure-CPU work, a handful of concurrent requests can saturate a typical multi-worker deployment. No authentication is required, and no data is disclosed or modified.
Fixed in 3.2.12 and 3.3.0rc1.
Validation now enforces a per-document comparison budget. Once
MAX_FIELD_COMPARISONS (default 250_000) pairwise comparisons have been made, the
rule aborts and reports a GraphQLError instead of running to completion:
Query is too complex to validate for overlapping fields: exceeded 250000 field comparisons. Validation was aborted.
The default is far above what any realistic query reaches. It is a module-level constant and can be raised or lowered:
from graphql.validation.rules import overlapping_fields_can_be_merged
overlapping_fields_can_be_merged.MAX_FIELD_COMPARISONS = 1_000_000
Note the behaviour change: a pathological query that previously validated — slowly — now produces a validation error.
If you cannot upgrade, reject oversized documents before they reach validate().
Note that a request body-size limit must be applied to the decompressed body to be
effective here, since the payload is highly compressible. Removing
OverlappingFieldsCanBeMergedRule from the rule set is not a safe workaround: it is
a correctness rule required by the specification.
A body-size limit remains worthwhile after upgrading — the comparison budget bounds this specific rule, not overall request cost.
Reported by @BZHunt, who also reviewed the fixes before release. In memory of ZaX, who was the driving force behind this discovery.
Independently found and diagnosed by @bcmyguest, who also proposed a fix.
Talk to us about this
Find out whether this dependency (or others like it) is actually reachable in your codebase.
Get help patching, or building dependency-scanning into your pipeline going forward.
A secure code review or supply chain review finds this before an advisory does.