CVE-2026-81633 Overview
CVE-2026-81633 is an improper input validation vulnerability [CWE-20] in the ash_graphql library maintained by the ash-project. The flaw resides in AshGraphql.Graphql.Resolver.resolve_node/2, which decodes client-supplied relay global IDs without validating the type segment. An unauthenticated remote client can submit a crafted node(id: ...) query that triggers an unhandled KeyError in the resolver. The exception aborts execution before rescue handlers convert it into a GraphQL error, potentially exposing a stacktrace. The issue affects ash_graphql versions from 0.27.0 up to (but not including) 1.11.0.
Critical Impact
Unauthenticated attackers can crash relay node resolvers and induce stacktrace disclosure by supplying a valid atom type segment that is not a relay-exposed type.
Affected Products
- ash-project ash_graphql >= 0.27.0, < 1.11.0
- Elixir/Absinthe applications exposing relay node(id: ...) queries via ash_graphql
- Downstream services depending on vulnerable ash_graphql releases
Discovery Timeline
- 2026-08-30 - CVE-2026-81633 published to NVD
- 2026-09-01 - Last updated in NVD database
Technical Details for CVE-2026-81633
Vulnerability Analysis
The vulnerability originates in the relay node resolver of ash_graphql. When a GraphQL client issues a node(id: ...) query, the resolver invokes decode_relay_id/1, which base64-decodes the supplied global ID and splits it on : to produce a type segment and a primary key. The decoded type is then passed directly to Map.fetch!(type_to_domain_and_resource_map, type).
Because Map.fetch!/2 raises when a key is missing, any valid atom that is not a relay-exposed type causes the resolver to abort. The raised exception escapes before Absinthe's rescue handlers in the resolve/2 clauses execute. The client receives an unhandled error and, depending on server configuration, the response may include a stacktrace revealing internal module names, function arities, and file paths.
Common resource names in Ash applications are predictable, so guessing candidate atoms is trivial. Repeated exploitation degrades the availability of the relay node endpoint and produces information disclosure through error output. This is classified as a GraphQL API improper input validation issue.
Root Cause
The resolver trusts the decoded type segment of a client-controlled global ID and performs a strict map lookup with Map.fetch!/2 instead of a fallible Map.fetch/2. Missing validation between decoding and lookup allows an unhandled KeyError to propagate out of the resolver.
Attack Vector
An unauthenticated remote attacker sends a GraphQL query invoking the relay node(id: ...) field with a base64-encoded string of the form <atom>:<value>, where <atom> is any valid Elixir atom name that is not registered as a relay-exposed type. The resolver raises KeyError, bypasses the normal GraphQL error path, and returns an unhandled server error.
) do
case AshGraphql.Resource.decode_relay_id(id) do
{:ok, %{type: type, id: primary_key}} ->
- {domain, resource} = Map.fetch!(type_to_domain_and_resource_map, type)
- # We can be sure this returns something since we check this at compile time
- query = AshGraphql.Resource.primary_key_get_query(resource, all_domains)
-
- # We pass relay_ids? as false since we pass the already decoded primary key
- put_in(resolution.arguments.id, primary_key)
- |> resolve({domain, resource, query, false})
+ case Map.fetch(type_to_domain_and_resource_map, type) do
+ {:ok, {domain, resource}} ->
+ query = AshGraphql.Resource.primary_key_get_query(resource, all_domains)
+
+ # We pass relay_ids? as false since we pass the already decoded primary key
+ put_in(resolution.arguments.id, primary_key)
+ |> resolve({domain, resource, query, false})
+
+ # A client-supplied id whose type segment is not a known node type must
+ # become a GraphQL error, not an unhandled KeyError.
+ :error ->
+ Absinthe.Resolution.put_result(resolution, {:error, "Invalid node id"})
+ end
{:error, _reason} = error ->
Absinthe.Resolution.put_result(resolution, error)
Source: GitHub Commit c8863ed. The patch replaces Map.fetch!/2 with Map.fetch/2 and returns an Invalid node id GraphQL error for unknown types.
Detection Methods for CVE-2026-81633
Indicators of Compromise
- GraphQL requests targeting the node(id: ...) field with unusual or malformed base64-encoded IDs.
- Application logs containing KeyError exceptions originating in AshGraphql.Graphql.Resolver.resolve_node/2.
- HTTP 500 responses or GraphQL responses containing Elixir stacktraces referencing ash_graphql modules.
Detection Strategies
- Inspect GraphQL request logs for node queries whose decoded IDs contain type segments not present in the schema's relay-exposed types.
- Correlate spikes in KeyError exceptions with inbound GraphQL traffic to identify probing activity.
- Instrument Absinthe middleware to log rejected relay IDs and alert on repeated failures from the same client.
Monitoring Recommendations
- Aggregate Elixir/Erlang application error telemetry (for example, via :telemetry or Logger) and monitor for unhandled resolver exceptions.
- Track the ratio of successful to failed node(id: ...) resolutions per source IP and flag anomalous rates.
- Ensure GraphQL responses in production do not include stacktraces, and alert if debug-mode error payloads reach clients.
How to Mitigate CVE-2026-81633
Immediate Actions Required
- Upgrade ash_graphql to version 1.11.0 or later, which replaces Map.fetch!/2 with a safe Map.fetch/2 fallback.
- Audit dependency manifests (mix.exs, mix.lock) across all Elixir services to enumerate exposure to vulnerable versions.
- Disable stacktrace exposure in GraphQL error responses in production environments until the upgrade is deployed.
Patch Information
The fix is available in ash_graphql1.11.0. See the GitHub Security Advisory GHSA-mrgv-g7gf-r96h, the CNA advisory, and the OSV vulnerability report for authoritative remediation guidance.
Workarounds
- Add an Absinthe middleware that validates the decoded type segment against the known relay type map before the resolver executes.
- Wrap resolve_node/2 invocations with an application-level rescue that converts KeyError into a generic Invalid node id GraphQL error.
- Rate-limit unauthenticated GraphQL traffic to reduce the throughput of probing attempts against the node field.
# Update the dependency in mix.exs and refresh the lockfile
# {:ash_graphql, "~> 1.11.0"}
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.

