VYPR
High severity8.2NVD Advisory· Published May 8, 2026· Updated May 13, 2026

CVE-2026-42793

CVE-2026-42793

Description

Allocation of Resources Without Limits or Throttling vulnerability in absinthe-graphql absinthe allows unauthenticated denial of service via atom table exhaustion when parsing attacker-controlled GraphQL SDL.

Multiple Blueprint.Draft.convert/2 implementations in Absinthe's SDL language modules call String.to_atom/1 on attacker-controlled names from parsed GraphQL SDL documents, including directive names, field names, type names, and argument names. Because atoms are never garbage-collected and the BEAM atom table has a fixed limit (default 1,048,576), each unique name permanently consumes one slot. An attacker can exhaust the atom table by submitting SDL documents containing enough unique names, causing the Erlang VM to abort with system_limit and taking down the entire node.

Any application that passes attacker-controlled GraphQL SDL through Absinthe's parser is exposed — for example, a schema-upload endpoint, a federation gateway that ingests remote SDL, or any developer tool that runs the parser over user-supplied documents.

This issue affects absinthe: from 1.5.0 before 1.10.2.

Affected products

1

Patches

1
dd842b938e38

Merge commit from fork

https://github.com/absinthe-graphql/absintheCurtis SchiewekMay 8, 2026via ghsa
4 files changed · +225 1
  • lib/absinthe/phase/document/validation/executable_definitions.ex+54 0 added
    @@ -0,0 +1,54 @@
    +defmodule Absinthe.Phase.Document.Validation.ExecutableDefinitions do
    +  @moduledoc """
    +  Implements GraphQL spec §5.1.1 "Executable Definitions": a document
    +  submitted for execution must contain only OperationDefinition and
    +  FragmentDefinition. Any TypeSystemDefinition or TypeSystemExtension
    +  is invalid for execution and is rejected here.
    +
    +  Runs between Phase.Parse and Phase.Blueprint in the `for_document` pipeline.
    +  """
    +  use Absinthe.Phase
    +
    +  alias Absinthe.{Blueprint, Language, Phase}
    +
    +  @executable_definitions [Language.OperationDefinition, Language.Fragment]
    +
    +  @spec run(Blueprint.t(), Keyword.t()) :: Phase.result_t()
    +  def run(%Blueprint{input: %Language.Document{} = document} = blueprint, options \\ []) do
    +    case Enum.reject(document.definitions, &(&1.__struct__ in @executable_definitions)) do
    +      [] ->
    +        {:ok, blueprint}
    +
    +      type_definitions ->
    +        errors =
    +          Enum.map(type_definitions, fn node ->
    +            %Phase.Error{
    +              phase: __MODULE__,
    +              message: "#{label(node)} is not an executable definition",
    +              locations: [node.loc]
    +            }
    +          end)
    +
    +        blueprint = update_in(blueprint.execution.validation_errors, &(errors ++ &1))
    +
    +        case Map.new(options) do
    +          %{jump_phases: true, result_phase: result_phase} -> {:jump, blueprint, result_phase}
    +          _ -> {:error, blueprint}
    +        end
    +    end
    +  end
    +
    +  defp label(node) do
    +    case node do
    +      %Language.DirectiveDefinition{name: name} -> "Directive `@#{name}`"
    +      %Language.EnumTypeDefinition{name: name} -> "Enum `#{name}`"
    +      %Language.InputObjectTypeDefinition{name: name} -> "Input object `#{name}`"
    +      %Language.InterfaceTypeDefinition{name: name} -> "Interface `#{name}`"
    +      %Language.ObjectTypeDefinition{name: name} -> "Type `#{name}`"
    +      %Language.ScalarTypeDefinition{name: name} -> "Scalar `#{name}`"
    +      %Language.SchemaDeclaration{} -> "A schema definition"
    +      %Language.TypeExtensionDefinition{definition: %{name: name}} -> "An extension of `#{name}`"
    +      %Language.UnionTypeDefinition{name: name} -> "Union `#{name}`"
    +    end
    +  end
    +end
    
  • lib/absinthe/pipeline.ex+1 0 modified
    @@ -57,6 +57,7 @@ defmodule Absinthe.Pipeline do
           {Phase.Telemetry, Keyword.put(options, :event, [:execute, :operation, :start])},
           # Parse Document
           {Phase.Parse, options},
    +      {Phase.Document.Validation.ExecutableDefinitions, options},
           # Convert to Blueprint
           {Phase.Blueprint, options},
           # Find Current Operation (if any)
    
  • test/absinthe/phase/document/validation/executable_definitions_test.exs+162 0 added
    @@ -0,0 +1,162 @@
    +defmodule Absinthe.Phase.Document.Validation.ExecutableDefinitionsTest do
    +  @phase Absinthe.Phase.Document.Validation.ExecutableDefinitions
    +
    +  use Absinthe.PhaseCase, phase: @phase, schema: __MODULE__.Schema, async: true
    +
    +  defmodule Schema do
    +    use Absinthe.Schema
    +
    +    query do
    +      field :hello, :string do
    +        resolve fn _, _, _ -> {:ok, "world"} end
    +      end
    +    end
    +  end
    +
    +  describe "documents containing only executable definitions" do
    +    test "passes for a single anonymous operation" do
    +      assert {:ok, %{execution: %{validation_errors: []}}, _} = run_phase("{ hello }", [])
    +    end
    +
    +    test "passes for a named query" do
    +      assert {:ok, %{execution: %{validation_errors: []}}, _} =
    +               run_phase("query hello { hello }", [])
    +    end
    +
    +    test "passes for a mutation" do
    +      assert {:ok, %{execution: %{validation_errors: []}}, _} =
    +               run_phase("mutation hello { hello }", [])
    +    end
    +
    +    test "passes for a subscription" do
    +      assert {:ok, %{execution: %{validation_errors: []}}, _} =
    +               run_phase("subscription hello { hello }", [])
    +    end
    +
    +    test "passes for an operation plus a fragment" do
    +      query = """
    +      fragment Bar on Query { hello }
    +      query Foo { ...Bar }
    +      """
    +
    +      assert {:ok, %{execution: %{validation_errors: []}}, _} = run_phase(query, [])
    +    end
    +  end
    +
    +  describe "documents containing type system definitions are rejected" do
    +    test "rejects a directive definition" do
    +      document = """
    +      directive @skip on FIELD
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Directive `@skip` is not an executable definition" = message
    +    end
    +
    +    test "rejects an object type definition" do
    +      document = """
    +      type Foo { field: String }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Type `Foo` is not an executable definition" = message
    +    end
    +
    +    test "rejects an enum type definition" do
    +      document = """
    +      enum Color { RED GREEN BLUE }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Enum `Color` is not an executable definition" = message
    +    end
    +
    +    test "rejects an input object type definition" do
    +      document = """
    +      input FooInput { field: String }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Input object `FooInput` is not an executable definition" = message
    +    end
    +
    +    test "rejects an interface type definition" do
    +      document = """
    +      interface Node { id: ID! }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Interface `Node` is not an executable definition" = message
    +    end
    +
    +    test "rejects a scalar type definition" do
    +      document = """
    +      scalar DateTime
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Scalar `DateTime` is not an executable definition" = message
    +    end
    +
    +    test "rejects a union type definition" do
    +      document = """
    +      union Result = Foo | Bar
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "Union `Result` is not an executable definition" = message
    +    end
    +
    +    test "rejects a schema definition" do
    +      document = """
    +      schema { query: Query }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "A schema definition is not an executable definition" = message
    +    end
    +
    +    test "rejects a type extension" do
    +      document = """
    +      extend type Query { extra: String }
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: [error]}}, _} = run_phase(document, [])
    +      assert %Absinthe.Phase.Error{phase: @phase, message: message} = error
    +      assert "An extension of `Query` is not an executable definition" = message
    +    end
    +
    +    test "produces one error per offending definition" do
    +      document = """
    +      directive @one on FIELD
    +      type Foo { field: String }
    +      scalar DateTime
    +      query { hello }
    +      """
    +
    +      assert {:error, %{execution: %{validation_errors: errors}}, _} = run_phase(document, [])
    +      assert Enum.all?(errors, &match?(%Absinthe.Phase.Error{phase: @phase}, &1))
    +      messages = Enum.map(errors, &(&1.message))
    +      assert "Directive `@one` is not an executable definition" = Enum.at(messages, 0)
    +      assert "Type `Foo` is not an executable definition" = Enum.at(messages, 1)
    +      assert "Scalar `DateTime` is not an executable definition" = Enum.at(messages, 2)
    +    end
    +  end
    +end
    
  • test/absinthe/pipeline_test.exs+8 1 modified
    @@ -21,7 +21,14 @@ defmodule Absinthe.PipelineTest do
             Pipeline.for_document(Schema)
             |> Pipeline.upto(Phase.Blueprint)
     
    -      assert {:ok, %Blueprint{}, [Phase.Blueprint, Phase.Parse, Phase.Telemetry, Phase.Init]} =
    +      assert {:ok, %Blueprint{},
    +              [
    +                Phase.Blueprint,
    +                Phase.Document.Validation.ExecutableDefinitions,
    +                Phase.Parse,
    +                Phase.Telemetry,
    +                Phase.Init
    +              ]} =
                    Pipeline.run(@query, pipeline)
         end
       end
    

Vulnerability mechanics

AI mechanics synthesis has not run for this CVE yet.

References

6

News mentions

0

No linked articles in our index yet.