{"affected":[{"ranges":[{"events":[{"introduced":"793c1147210388587defb649f25f293ffbfa6b1b"},{"fixed":"c81f12265919c34a0d5134f1905a4eb4edaea1c2"},{"fixed":"7154e8781da9fd016a973ea01776a011c8b5726a"},{"fixed":"cd158efacdc10b30232864251747dff9b8387dfe"},{"fixed":"ee5cc23f8928aa5a0f25bb7f28e42ecfc7b7d396"}],"repo":"https://github.com/elixir-lang/elixir","type":"GIT"}]}],"aliases":["GHSA-rp5q-xqhq-926h","CVE-2026-115274"],"credits":[{"name":"David Vieira Kurz","type":"FINDER"},{"name":"Peter Ullrich","type":"FINDER"},{"name":"David Vieira Kurz","type":"REPORTER"},{"name":"José Valim","type":"REMEDIATION_DEVELOPER"},{"name":"Jonatan Männchen / EEF","type":"COORDINATOR"}],"database_specific":{"capec_ids":["CAPEC-130"],"cpe_ids":["cpe:2.3:a:elixir-lang:elixir:*:*:*:*:*:*:*:*"],"cwe_ids":["CWE-1284"]},"details":"## Summary\n\nImproper Validation of Specified Quantity in Input vulnerability in the Elixir standard library's `JSON` module allows an unauthenticated attacker who supplies a JSON document to cause a denial of service through CPU and memory exhaustion.\n\n`JSON.decode/1` and `JSON.decode!/1` convert each integer literal with `erlang:binary_to_integer/1` and do not bound its length. A literal of one million digits decodes successfully but costs 100 to 300 times as much CPU as a string of the same length, so one request body of a few megabytes holds a scheduler for about a second. A literal of more than about 1.29 million digits raises `SystemLimitError`. Applications generated by `phx.new` decode request bodies with `JSON` in `Plug.Parsers`, before routing and authentication.\n\nThis issue affects elixir: from 1.18.0-rc.0 before 1.18.6, from 1.19.0-rc.0 before 1.19.7, and from 1.20.0-rc.0 before 1.20.5.\n\n## Details\n\n**1. Default integer decoder**\n\n`JSON.decode/1` and `JSON.decode!/1` call `JSON.decode/3` without an `integer` decoder. Up to the removal of Erlang/OTP 26 support, `JSON.decode/3` used the vendored `elixir_json` module, whose default is `fun erlang:binary_to_integer/1`. It now calls OTP's `:json.decode/3`, which has the same default. The `JSON.decode/3` documentation names `&String.to_integer/1` as the default, which converts the same way.\n\n**2. Unbounded conversion**\n\nThe decoder scans the digits of an integer literal and passes the whole run to the conversion. Nothing limits the run's length, so the cost grows faster than linearly with the number of digits. At one million digits an integer literal costs 100 to 300 times as much CPU as a string literal of the same length. Above about 1.29 million digits the conversion raises `SystemLimitError`, which `JSON.decode/3` does not catch.\n\n**3. Reach**\n\n`phx.new` sets `config :phoenix, :json_library, JSON`, and the generated endpoint passes `Phoenix.json_library()` to `Plug.Parsers` as `json_decoder`. `Plug.Parsers` runs before routing and authentication and accepts bodies up to 8 MB by default. It rescues the `SystemLimitError` into a 400 response after the CPU cost is spent. Jason, the previous default, rejects integer literals longer than its `decoding_integer_digit_limit` (1024 by default) before conversion.\n\n## Proof of concept\n\n1. Build `body = \"[\" <> Enum.map_join(1..7, \",\", fn _ -> String.duplicate(\"9\", 1_000_000) end) <> \"]\"`, a body of about 7 MB.\n2. `JSON.decode(body)` returns `{:ok, ...}` after about one second of CPU.\n3. `JSON.decode(String.duplicate(\"9\", 1_000_000))` takes 100 to 300 times as long as `JSON.decode(\"\\\"\" <> String.duplicate(\"9\", 1_000_000) <> \"\\\"\")`.\n4. `JSON.decode(String.duplicate(\"9\", 1_400_000))` raises `SystemLimitError`.\n\n## Impact\n\nAn unauthenticated client that sends a JSON body with integer literals of about a million digits makes the server spend about a second of CPU per request and allocate large bignums. Sustained requests exhaust CPU on applications that decode untrusted JSON with `JSON`, including new Phoenix applications by default.\n\n## Workarounds\n\nDecode untrusted JSON with an `integer` decoder that rejects long digit runs before conversion:\n\n```elixir\ndefmodule SafeJSON do\n  def decode!(binary) do\n    case JSON.decode(binary, :ok, integer: &safe_integer/1) do\n      {decoded, :ok, \"\"} -> decoded\n      _ -> raise ArgumentError, \"invalid JSON\"\n    end\n  end\n\n  defp safe_integer(digits) when byte_size(digits) <= 1024, do: String.to_integer(digits)\n  defp safe_integer(_digits), do: raise(ArgumentError, \"integer too long\")\nend\n```\n\nIn Phoenix, pass `SafeJSON` to `Plug.Parsers` as `json_decoder` instead of `Phoenix.json_library()`.","id":"EEF-CVE-2026-115274","modified":"2026-10-30T09:01:55.387653Z","published":"2026-10-30T08:57:16.847Z","references":[{"type":"ADVISORY","url":"https://github.com/elixir-lang/elixir/security/advisories/GHSA-rp5q-xqhq-926h"},{"type":"WEB","url":"https://cna.erlef.org/cves/CVE-2026-115274.html"},{"type":"WEB","url":"https://github.com/elixir-lang/elixir/commit/793c1147210388587defb649f25f293ffbfa6b1b"},{"type":"FIX","url":"https://github.com/elixir-lang/elixir/commit/c81f12265919c34a0d5134f1905a4eb4edaea1c2"},{"type":"FIX","url":"https://github.com/elixir-lang/elixir/commit/7154e8781da9fd016a973ea01776a011c8b5726a"},{"type":"FIX","url":"https://github.com/elixir-lang/elixir/commit/cd158efacdc10b30232864251747dff9b8387dfe"},{"type":"FIX","url":"https://github.com/elixir-lang/elixir/commit/ee5cc23f8928aa5a0f25bb7f28e42ecfc7b7d396"}],"related":[],"schema_version":"1.7.3","severity":[{"score":"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:N/VI:N/VA:L/SC:N/SI:N/SA:N","type":"CVSS_V4"}],"summary":"Unbounded integer parsing in JSON.decode/1 enables CPU and memory exhaustion denial of service in Elixir","upstream":[]}