Magento GraphQL: introspection exposure and query-complexity DoS
Magekwik Security 6 min read
Magento's /graphql endpoint answers introspection in production and rides on webonyx/graphql-php, whose OverlappingFieldsCanBeMerged validator can degrade to quadratic time — a single crafted inline-fragment query can burn a php-fpm worker before any resolver runs. The mechanism, how to detect it, and the isolated patch chain.
What it is
Two related exposures sit on the Magento GraphQL endpoint (/graphql). The first is a misconfiguration: schema introspection is answerable in production, so any anonymous client can dump the entire type system. The second is a class of algorithmic-complexity denial of service in webonyx/graphql-php — the library Adobe Commerce bundles as its GraphQL runtime — where query validation runs in quadratic time and turns one small request into seconds of pinned CPU. Chained, an attacker reads the schema, then hand-crafts requests that exhaust the php-fpm pool before a single resolver executes.
Root cause
Introspection has been answerable in production mode since 2.3.2; the endpoint resolves __schema and __type for unauthenticated callers by default. That is intended behaviour, not a bug — but it is rarely what an operator wants facing the open internet.
The DoS root cause is deeper. Magento ships Magento\Framework\GraphQl\Query\QueryComplexityLimiter with defaults of queryComplexity: 300 and queryDepth: 20. Those counters score fields. They do not model the cost inside graphql-php's OverlappingFieldsCanBeMerged validation rule, which compares selected fields pairwise and runs before complexity gating meaningfully protects you. When many fields share a response name, the comparison degrades to O(n²); nested inline fragments compound it to roughly O(n² × m²). The original quadratic bug was tracked upstream in graphql-js (the JavaScript reference implementation) as CVE-2023-26144; graphql-php later ported the same rule and its memoization fix — but that memoization only covered named fragments, leaving inline fragments as an open bypass.
How attackers abuse it
- Send a standard introspection query to enumerate every type, custom-module field, mutation name, and deprecated field — reconnaissance for targeted resolver abuse.
- Use aliasing and batching to multiply resolver work while staying under the complexity ceiling.
- Trigger the validator pathology directly. A ~364 KB query built from 200 outer and 100 inner inline fragments selecting identical response names consumes ~117 seconds of CPU during validation alone — no resolver reached, no timeout fired.
- Each such request occupies one full worker. Against a typical php-fpm pool of 5–50 workers, a handful of parallel requests pins the pool. The body is sent gzip-compressed, so naive request-size limits at the edge decompress it and pass it straight to the validator.
Who got hit / real examples
This is a live, cataloged vulnerability chain in the exact library Adobe Commerce depends on via Composer — so it lands on any storefront running an unpatched vendor tree, regardless of custom code.
OverlappingFieldsCanBeMerged DoS timeline (graphql-js → graphql-php)
- CVE-2023-26144 — the ORIGINAL quadratic
OverlappingFieldsCanBeMergedcost, in graphql-js (npmgraphql16.3.0–<16.8.1), fixed with named-fragment memoization. Magento's graphql-php later inherited the same rule. - CVE-2026-40476 (GHSA-68jq-c3rv-pcrr) — quadratic cost on repeated same-response-name fields. Affected
≤ 15.31.4, fixed in 15.31.5. CVSS 6.9. Disclosed 2026-04-11. - GHSA-fc86-6rv6-2jpm — the inline-fragment bypass of the prior fix. Affected
< 15.32.2, fixed in 15.32.2. CVSS 7.5 (availability impact High). Disclosed 2026-04-24.
How to check
curl -s https://store.example.com/graphql \
-H 'Content-Type: application/json' \
-d '{"query":"{ __schema { queryType { name } types { name } } }"}' \
| head -c 400
# A populated __schema.types array = introspection is open.
composer show webonyx/graphql-php | grep versions
# Anything below 15.32.2 is exposed to the inline-fragment DoS.
composer why webonyx/graphql-php # confirms it comes via magento/framework
Also confirm whether anyone has overridden the limiter defaults — grep your di.xml tree for QueryComplexityLimiter. If the defaults are untouched, depth 20 / complexity 300 is all that stands between you and resolver amplification.
How to fix it
- Patch the dependency first. This is the isolated, highest-value fix — it closes the CVE chain without touching your own code.
- Disable introspection at the edge or in config for production.
- Tighten the limiter and cap request body size on
/graphql, including compressed bodies.
composer update webonyx/graphql-php --with-dependencies
# Target >= 15.32.2. On Adobe Commerce, apply the corresponding
# vendor patch via the Quality Patches Tool rather than editing vendor/.
'graphql' => [
'disable_introspection' => true,
],
<type name="Magento\Framework\GraphQl\Query\QueryComplexityLimiter">
<arguments>
<argument name="queryDepth" xsi:type="number">10</argument>
<argument name="queryComplexity" xsi:type="number">150</argument>
</arguments>
</type>
At the edge, put a hard client_max_body_size on the /graphql location and rate-limit it independently of the rest of the store — a legitimate storefront query is kilobytes, not hundreds of kilobytes. Body-size and rate limits are the durable control here, because complexity scoring alone never sees the validator cost that these CVEs exploit.
References & sources
Primary sources — advisories, vendor research, and the CVE record. Verify against these, not us.
- GHSA-fc86-6rv6-2jpm — quadratic validation via inline fragments
- CVE-2026-40476 / GHSA-68jq-c3rv-pcrr — OverlappingFieldsCanBeMerged quadratic DoS
- Adobe Commerce — GraphQL security configuration
- magento/devdocs #3717 — introspection allowed in production
- Snyk — CVE-2023-26144 DoS in graphql (graphql-js origin)