Magento GraphQL: introspection exposure and query-complexity DoS
Kishore Adobe Commerce Certified Professional 6 min read
Covers events from 11 April 2026.
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. 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. This is 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 is reached and no timeout fires.
- 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 rather than against 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)