Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-53426

CVE-2026-53426: MDEx Atom Exhaustion DoS Vulnerability

CVE-2026-53426 is a denial-of-service vulnerability in MDEx that allows attackers to exhaust the Erlang atom table through crafted JSON documents, crashing the VM. This post covers technical details, affected versions, and mitigations.

Published:

CVE-2026-53426 Overview

CVE-2026-53426 is an unbounded resource allocation flaw in the leandrocp/mdex Elixir library. The MDEx.parse_document/2 function accepts a {:json, json} source and passes attacker-controlled node_type values through Module.concat/1, which invokes String.to_atom/1. Because atoms on the BEAM virtual machine are never garbage collected, a crafted JSON document with unique node_type values at every nested node interns a permanent atom per node. A single request can exhaust the default atom table of roughly 1,048,576 entries and abort the entire Erlang VM. The flaw affects mdex versions from 0.4.3 before 0.13.2 and is classified as [CWE-770].

Critical Impact

Any application passing untrusted input to MDEx.parse_document with a {:json, ...} source is exposed to an unauthenticated denial-of-service that terminates every process on the Erlang node.

Affected Products

  • leandrocp/mdex Elixir library versions 0.4.3 through 0.13.1
  • Elixir and Erlang/BEAM applications that pass untrusted JSON to MDEx.parse_document/2
  • Phoenix and other Elixir web services integrating MDEx for Markdown rendering from JSON input

Discovery Timeline

  • 2026-06-29 - CVE-2026-53426 published to NVD
  • 2026-06-30 - Last updated in NVD database

Technical Details for CVE-2026-53426

Vulnerability Analysis

The vulnerability resides in the private json_to_node/1 function in lib/mdex.ex. When MDEx.parse_document/2 receives a {:json, json} tuple, the parser recursively walks the JSON document and extracts the node_type field from each object. That value is fed directly into Module.concat/1, which internally calls String.to_atom/1 to create a module reference.

Atoms are a fundamental primitive of the Erlang runtime and are stored in a global, non-reclaimable table. The default table size is approximately 1,048,576 entries. Once the limit is reached, the BEAM emergency-aborts with system_limit, killing every supervised process on the node.

Because the parser walks arbitrarily nested JSON, a compact document containing hundreds of thousands of unique node_type strings interns one atom per node. Repeated requests amplify the effect until the atom table is saturated.

Root Cause

The root cause is the use of Module.concat/1 on untrusted input. That function unconditionally creates new atoms and provides no safeguards. Elixir provides Module.safe_concat/1, which only resolves atoms that already exist and raises when an unknown value is supplied. The vulnerable code path failed to use the safe variant.

Attack Vector

An attacker submits a JSON document to any endpoint that forwards user input into MDEx.parse_document(source, format: :json). The document contains many nested objects, each with a distinct node_type string such as "nt_00001", "nt_00002", and so on. No authentication is required, and each request permanently consumes atom-table capacity until the node crashes.

text
// Security patch in lib/mdex.ex - json_to_node/1
 defp json_to_node(json) do
   {node_type, node} = Map.pop!(json, :node_type)
-  node_type = Module.concat([node_type])
+  node_type = Module.safe_concat([node_type])
   node = map_nodes(node)
   struct(node_type, node)
 end

Source: GitHub Commit 00fddf4. The fix swaps Module.concat/1 for Module.safe_concat/1, forcing the parser to reject node_type values that do not map to a pre-existing module atom.

Detection Methods for CVE-2026-53426

Indicators of Compromise

  • Sudden growth in the Erlang atom table observable via :erlang.system_info(:atom_count) approaching the :atom_limit value.
  • BEAM crash reports containing system_limit errors referencing atom creation.
  • Repeated HTTP requests posting JSON payloads with high-cardinality, machine-generated node_type string values to Markdown-processing endpoints.

Detection Strategies

  • Instrument application telemetry to poll :erlang.system_info(:atom_count) and alert when utilization exceeds a defined threshold (for example, 60 percent of :atom_limit).
  • Inspect request bodies handed to MDEx.parse_document for JSON structures with abnormally deep nesting and unique node_type values across siblings.
  • Correlate BEAM node restarts with request logs referencing MDEx JSON parsing routes to identify triggering payloads.

Monitoring Recommendations

  • Ship BEAM VM metrics (atom count, process count, memory) to a centralized observability platform and set alerts on rapid atom-table growth.
  • Log the size and depth of JSON documents accepted by MDEx endpoints and flag outliers for review.
  • Track abnormal node terminations and supervisor restart storms that could indicate atom-table exhaustion attacks.

How to Mitigate CVE-2026-53426

Immediate Actions Required

  • Upgrade mdex to version 0.13.2 or later in every Elixir application dependency graph.
  • Audit all call sites of MDEx.parse_document/2 and confirm whether they accept untrusted {:json, ...} input.
  • Temporarily disable JSON-source Markdown parsing on internet-facing endpoints until the upgrade is verified.

Patch Information

The fix landed in commit 00fddf4 and shipped in mdex 0.13.2. It replaces Module.concat/1 with Module.safe_concat/1 in json_to_node/1, preventing new atom creation from untrusted node_type values. Refer to the GitHub Security Advisory GHSA-923r-7vf4-5vw8 and the CNA CVE-2026-53426 record for full advisory details.

Workarounds

  • Reject or restrict the {:json, ...} source of MDEx.parse_document/2 when handling untrusted input; accept only the string Markdown source.
  • Validate incoming JSON against a strict schema that constrains node_type to a small allowlist before invoking MDEx.
  • Enforce request size and nesting-depth limits at the web layer to bound the number of node_type values processed per request.
bash
# Update mix.exs and fetch the patched release
# mix.exs
# defp deps do
#   [
#     {:mdex, "~> 0.13.2"}
#   ]
# end

mix deps.update mdex
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.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.