CVE-2026-81636 Overview
CVE-2026-81636 is an Allocation of Resources Without Limits or Throttling vulnerability [CWE-770] in the ash_graphql library for the Elixir Ash framework. An unauthenticated network client can bypass the configured GraphQL query-complexity cap by using Relay connection or keyset pagination arguments (first and last) instead of offset pagination (limit). Nested queries then materialize very large fan-outs while scoring as trivially cheap under the Absinthe max_complexity check. The vulnerability affects ash_graphql versions from 0.16.23 up to but not including 1.11.0.
Critical Impact
An unauthenticated attacker can force unbounded database reads against any exposed ash_graphql endpoint, exhausting database and application resources and producing a denial-of-service condition.
Affected Products
- ash-project ash_graphql>= 0.16.23
- ash-project ash_graphql< 1.11.0
- Elixir applications exposing GraphQL APIs built on the Ash framework with Relay or keyset pagination enabled
Discovery Timeline
- 2026-08-30 - CVE-2026-81636 published to NVD
- 2026-09-01 - Last updated in NVD database
Technical Details for CVE-2026-81636
Vulnerability Analysis
The defect lives in AshGraphql.Graphql.Resolver.query_complexity/3, the callback Absinthe invokes to score GraphQL queries before execution. The function multiplies child_complexity by the requested page size only when the argument map matches the :limit clause, which corresponds to offset pagination. Relay connections and keyset pagination pass first or last arguments instead, neither of which matches the :limit pattern. Those requests fall through to the catch-all clause that returns child_complexity + 1, so nested pagination fan-out is under-counted.
A nested query such as posts(first: 500) { edges { node { comments(first: 500) { ... } } } } scores near the minimum while the resolver reads 500 × 500 rows from the database. The same query written with limit would be rejected by the configured Absinthe max_complexity cap. The ash_graphql maintainers addressed this in version 1.11.0 by adding dedicated first and last clauses that clamp the multiplier to the action's page size.
Root Cause
The root cause is incomplete pattern coverage in the complexity function. Only offset-based pagination was accounted for, so cost estimation did not model the amplification introduced by Relay and keyset pagination.
Attack Vector
Exploitation requires no authentication and only network access to a GraphQL endpoint exposed by an affected ash_graphql version. An attacker crafts a deeply nested Relay query using first or last values approaching the action page size at each level. The request bypasses max_complexity, and the backend performs the full fan-out read.
end
end
+ # Relay connections and keyset pagination use `first`/`last` rather than `limit`;
+ # multiply by the page size here too, or the fan-out is under-counted and the
+ # complexity limit is bypassed.
+ def query_complexity(
+ %{first: first},
+ child_complexity,
+ _
+ )
+ when is_integer(first) do
+ if child_complexity == 0 do
+ 1
+ else
+ first * child_complexity
+ end
+ end
+
+ def query_complexity(
+ %{last: last},
+ child_complexity,
+ _
+ )
+ when is_integer(last) do
+ if child_complexity == 0 do
+ 1
+ else
+ last * child_complexity
+ end
Source: GitHub commit c3229f6 — the patch adds first and last clauses so the resolver multiplies child_complexity by the requested page size, restoring accurate cost accounting.
Detection Methods for CVE-2026-81636
Indicators of Compromise
- Inbound GraphQL POST requests containing deeply nested selection sets that use first: or last: arguments with large integer values
- Sudden spikes in database query duration, row-read counts, or connection-pool saturation correlated with GraphQL traffic
- Application logs showing long-running ash_graphql resolver invocations against paginated relationships
Detection Strategies
- Parse GraphQL request bodies at the ingress layer and flag queries whose nesting depth combined with the product of first/last values exceeds a defined budget
- Instrument Absinthe with a custom complexity logger to record accepted queries and their computed complexity, then alert on queries that read many rows while reporting low complexity
- Correlate authentication state with GraphQL traffic volume to identify unauthenticated clients issuing paginated queries
Monitoring Recommendations
- Track database rows-read and query latency metrics per GraphQL operation name
- Monitor Elixir BEAM memory and process counts for anomalous growth tied to GraphQL request handling
- Alert on repeated identical or templated GraphQL queries from a single source IP that trigger heavy backend reads
How to Mitigate CVE-2026-81636
Immediate Actions Required
- Upgrade ash_graphql to version 1.11.0 or later across all Elixir applications that expose GraphQL endpoints
- Audit Ash resource actions to confirm each paginated action defines a bounded default_page_size and max_page_size
- Rate-limit unauthenticated GraphQL traffic at the load balancer or reverse proxy until the upgrade is deployed
Patch Information
The fix is available in ash_graphql1.11.0. See the GitHub Security Advisory GHSA-mwc4-r9fc-h6mg, the CNA advisory from EEF, the OSV entry EEF-CVE-2026-81636, and the upstream commit c3229f6.
Workarounds
- Lower the Absinthe max_complexity value to a threshold that rejects even under-counted nested queries
- Enforce a strict maximum GraphQL query depth using an Absinthe middleware or pre-execution rule
- Require authentication for any GraphQL operation that exposes paginated relationships
- Cap first and last arguments at the schema level by validating input ranges before resolver execution
# Update the ash_graphql dependency in mix.exs, then fetch and compile
# mix.exs
# {:ash_graphql, "~> 1.11"}
mix deps.update ash_graphql
mix deps.get
mix compile --force
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.