Jun 4, 2026·6 min read·24 visits
A recursive fragment loop triggers a RecursionError in Python, crashing worker threads/processes and resulting in complete Denial of Service.
An application-level Denial of Service vulnerability exists in the Strawberry GraphQL library (versions 0.71.0 through 0.315.6) due to uncontrolled recursion within the QueryDepthLimiter and MaxAliasesLimiter extensions when processing circular fragment references.
Strawberry GraphQL is an open-source Python library designed to build GraphQL APIs using type hints. The library provides optional security extensions to safeguard endpoints against resource exhaustion attacks. Two of these extensions, QueryDepthLimiter and MaxAliasesLimiter, are designed to restrict the complexity of incoming queries before execution.\n\nThese safety components are exposed to the public attack surface because they parse and validate user-supplied GraphQL queries. When an unauthenticated remote user submits a query, the validation engine processes the payload to enforce configured limits on query depth and aliases. If a query contains nested or cyclic structures, the validation logic traverses the entire document structure.\n\nA design flaw in versions prior to 0.315.7 allows an attacker to exploit the validation logic itself. By sending queries with circular fragment definitions, an attacker can bypass the safety controls and trigger an unhandled exception. This results in an application-level Denial of Service (DoS) by crashing the active Python worker thread or process.
The core issue is classified as CWE-674: Uncontrolled Recursion, which leads to CWE-400: Uncontrolled Resource Consumption. Within the QueryDepthLimiter extension, the determine_depth function traverses the selection sets of a GraphQL query. When the traversal encounters a FragmentSpreadNode, it retrieves the corresponding fragment definition and recursively invokes determine_depth to evaluate its inner depth.\n\nIn vulnerable versions of the library, the recursion occurred without tracking state or maintaining a history of previously visited nodes in the active path. The implementation did not pass a collection of traversed fragment identifiers down the call stack. Because of this omission, the algorithm cannot detect when it re-evaluates a fragment that is already present in its current call hierarchy.\n\nWhen a client submits a query where Fragment A references Fragment B, and Fragment B references Fragment A, a mutual recursion loop is established. The determine_depth function calls itself infinitely, executing until it consumes all available stack frames. Python runtime environments strictly limit the maximum recursion depth, throwing a RecursionError and terminating execution when the limit is breached.
The fix implemented in commit a69221fb0b86583ceb5755758b294c8319021fd1 introduces path-based cycle detection using Python's immutable frozenset type. This prevents infinite loop execution by tracking the names of resolved fragments across the active execution branch. The determine_depth function was updated to accept a visited_fragments parameter.\n\npython\n# Patched version of determine_depth\ndef determine_depth(\n # ... standard parameters ...\n visited_fragments: frozenset[str] | None = None,\n) -> int:\n if visited_fragments is None:\n visited_fragments = frozenset()\n\n\nDuring traversal, when a FragmentSpreadNode is encountered, the engine checks if the fragment name already exists in the visited_fragments set. If a match is found, the function returns a depth of 0 immediately, breaking the loop safely. If the fragment is unvisited, the traversal continues, appending the current fragment name to a new frozenset using the union operator.\n\npython\n if isinstance(node, FragmentSpreadNode):\n fragment_name = node.name.value\n if fragment_name in visited_fragments:\n return 0 # Break circular recursion\n\n return determine_depth(\n node=fragments[fragment_name],\n # ... other parameters ...\n visited_fragments=visited_fragments | {fragment_name},\n )\n\n\nThis pattern is highly effective because frozenset is immutable. Using the union operator (visited_fragments | {fragment_name}) passes a distinct, branch-specific copy down the recursion tree. This avoids corrupting or sharing visited state with parallel, non-cyclic sibling query branches, ensuring both correctness and safety.\n\nmermaid\ngraph LR\n A["determine_depth(A)"] --> B["determine_depth(B)"]\n B --> C["determine_depth(A) - Triggers Break"]\n
An attacker can exploit this vulnerability with a single crafted HTTP POST request containing a circular fragment reference. Since the parsing and validation phases execute before any authentication middleware or resolvers, this attack does not require valid credentials. The target endpoint must have the QueryDepthLimiter or MaxAliasesLimiter extension enabled to be vulnerable.\n\nThe exploit payload defines two fragments that mutually spread each other. For example, Fragment A references B, and Fragment B references A. A top-level query then references Fragment A. When the GraphQL validation engine processes the query, it invokes the vulnerable depth limiter logic, initiating the recursive loop.\n\ngraphql\nfragment A on User {\n ...B\n}\nfragment B on User {\n ...A\n}\nquery Exploit {\n me {\n ...A\n } \n}\n\n\nOnce submitted, the worker thread begins processing determine_depth for Fragment A, which calls Fragment B, which calls Fragment A. Within milliseconds, the stack depth exceeds Python's threshold. The resulting RecursionError escapes the validation context, crashing the underlying process and denying service to other concurrent requests.
The primary impact of CVE-2026-47706 is a complete Denial of Service (DoS) of the application layer. When Python processes throw a RecursionError during query validation, the exception is frequently unhandled by the ASGI or WSGI application servers. This triggers a hard crash of the active worker process.\n\nIn standard deployments, application servers such as Gunicorn or Uvicorn maintain a fixed pool of worker processes to handle incoming requests. If an attacker sends concurrent requests containing the circular exploit payload, they can systematically crash every available worker process in the pool. This leads to a total service outage for all users.\n\nAlthough the vulnerability does not lead to remote code execution (RCE) or data confidentiality breaches, its ease of exploitation makes it a significant operational risk. Because the validation phase occurs prior to authentication, any unauthenticated user with network access to the GraphQL endpoint can trigger the crash. The CVSS score of 5.3 reflects this focused impact on service availability.
The primary mitigation is upgrading the strawberry-graphql package to version 0.315.7 or later. This version incorporates cycle-tracking logic into both QueryDepthLimiter and MaxAliasesLimiter to safely handle cyclic structures. Organizations should verify their dependency locks and rebuild containers to ensure the patch is applied.\n\nIf upgrading is not immediately feasible, teams can disable the QueryDepthLimiter and MaxAliasesLimiter extensions as a temporary workaround. Note that disabling these extensions will expose the application to other query complexity attacks. Therefore, this action should only be taken if alternative mitigations are implemented.\n\nAn alternative workaround involves deploying a GraphQL-aware Web Application Firewall (WAF) or an API gateway (such as Apollo Router or Envoy) at the network perimeter. The gateway can be configured to validate fragment relationships and reject queries containing circular definitions before they reach the upstream Python application servers.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:L| Product | Affected Versions | Fixed Version |
|---|---|---|
strawberry-graphql Strawberry GraphQL | >= 0.71.0, <= 0.315.6 | 0.315.7 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-674 / CWE-400 |
| Attack Vector | Network (AV:N) |
| CVSS Score | 5.3 (Medium) |
| Exploit Status | Proof of Concept Available |
| CISA KEV Status | Not Listed |
| Impact | Availability (Denial of Service) |
The software directs a function to call itself recursively without a mechanism to limit the recursion depth, leading to stack exhaustion.
A remote code execution vulnerability exists in Microsoft .NET due to untrusted data deserialization within the PresentationCore component. The flaw stems from implicit auto-conversion flags during clipboard data retrieval operations.
An out-of-bounds write vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio allows unauthenticated remote attackers to execute arbitrary code via crafted pixel-shader bytecode or auto-converted clipboard payloads.
ASP.NET Core contains a thread deadlock vulnerability (CWE-833) that allows an unauthenticated remote attacker to cause a denial of service over the network by exhausting server thread pool resources.
CVE-2026-70035 is a high-severity Remote Code Execution (RCE) vulnerability in Microsoft .NET Framework, .NET runtime, and Visual Studio caused by improper handling during the deserialization of untrusted data (CWE-502). An unauthenticated attacker can execute arbitrary code within the context of the affected process by delivering a crafted serialized payload.
An elevation of privilege vulnerability exists in ASP.NET Core and Microsoft Visual Studio due to improper synchronization during concurrent thread execution. Authenticated remote attackers with low privileges can exploit a timing window to manipulate shared resources and assume higher-level execution contexts.
A path traversal vulnerability (CWE-22) in Contao CMS allows unauthenticated remote attackers to bypass directory boundary restrictions in ImagesController and access files within the project directory.