CVE-2026-8469 Overview
CVE-2026-8469 is a denial-of-service vulnerability in the phoenix_storybook library by phenixdigital, an Elixir component documentation tool built on Phoenix LiveView. Multiple LiveView event handlers invoke String.to_atom/1 on attacker-controlled parameter strings without validation. Because BEAM atoms are never garbage-collected, each unique string becomes a permanent allocation in the atom table. An unauthenticated remote attacker can reach the default ceiling of approximately 1,048,576 atoms, causing the entire BEAM virtual machine to abort. This terminates every application running on the node, not just the storybook component. The flaw is classified as CWE-770 Allocation of Resources Without Limits or Throttling and affects phoenix_storybook versions from 0.2.0 before 1.1.0.
Critical Impact
Unauthenticated network attackers can crash the entire Erlang VM hosting Phoenix Storybook, taking down all colocated applications on the same BEAM node.
Affected Products
- phenixdigital/phoenix_storybook versions >= 0.2.0 and < 1.1.0
- Any Phoenix LiveView application that mounts phoenix_storybook routes
- BEAM nodes hosting affected phoenix_storybook releases alongside other Elixir/Erlang applications
Discovery Timeline
- 2026-05-20 - CVE CVE-2026-8469 published to NVD
- 2026-05-20 - Last updated in NVD database
Technical Details for CVE-2026-8469
Vulnerability Analysis
The vulnerability resides in lib/phoenix_storybook/helpers/extra_assigns_helpers.ex. Four functions in the PhoenixStorybook.ExtraAssignsHelpers module intern attacker-controlled strings as atoms. handle_set_variation_assign/3 interns every key of the psb-assign params map. handle_toggle_variation_assign/3 interns the attr value from psb-toggle events. to_variation_id/2 interns elements of variation_id, and to_value/4 interns raw string values for attributes declared as :atom or :boolean.
Each unique string submitted through these LiveView event handlers permanently consumes one slot in the BEAM atom table. The default atom table limit is roughly 1,048,576 entries. Once exceeded, the BEAM emulator calls erts_exit and the entire node terminates.
Root Cause
The root cause is the use of String.to_atom/1 instead of String.to_existing_atom/1 or a whitelist lookup. Atoms in Erlang and Elixir live in a global, non-garbage-collected table. Converting unbounded user input into atoms violates the well-known BEAM safety principle that external data must never be interned without validation.
Attack Vector
An unauthenticated remote attacker sends a stream of LiveView events to any reachable Phoenix Storybook route. Each event carries unique random strings in psb-assign keys, psb-toggleattr values, variation_id elements, or attribute values for :atom/:boolean typed attributes. The server interns each unique string. After enough requests, the atom table fills and the BEAM node aborts.
// Patch excerpt from lib/phoenix_storybook/helpers/extra_assigns_helpers.ex
defp to_value("nil", _attr_id, _attributes, _context), do: nil
defp to_value(val, attr_id, attributes, context) when is_binary(val) do
- case declared_attr_type(attr_id, attributes) do
- :atom -> val |> existing_value_atom!(context) |> check_type!(:atom, context)
- :boolean -> val |> parse_boolean!(context) |> check_type!(:boolean, context)
- :integer -> val |> Integer.parse() |> check_type!(:integer, context)
- :float -> val |> Float.parse() |> check_type!(:float, context)
- _ -> val
+ case declared_attr(attr_id, attributes) do
+ %Attr{type: :atom, values: values} ->
+ val |> to_atom_value(values, context) |> check_type!(:atom, context)
+ %Attr{type: :boolean} ->
+ val |> to_boolean_value(context) |> check_type!(:boolean, context)
+ %Attr{type: :integer} ->
+ val |> Integer.parse() |> check_type!(:integer, context)
+ %Attr{type: :float} ->
+ val |> Float.parse() |> check_type!(:float, context)
+ _ ->
+ val
end
end
Source: GitHub commit 96d5246. The patch replaces unbounded atom creation with a lookup against declared attribute values, ensuring only known atoms are returned.
Detection Methods for CVE-2026-8469
Indicators of Compromise
- Steadily increasing erlang:system_info(:atom_count) values that approach the configured :atom_limit.
- High volumes of LiveView phx-event messages targeting storybook routes with randomized psb-assign, psb-toggle, or variation_id parameter names.
- Unexpected BEAM node crashes with erl_crash.dump files containing no_more_atoms or system_limit shutdown reasons.
Detection Strategies
- Instrument Phoenix Telemetry or :telemetry handlers to track atom table growth rate over time.
- Inspect web access logs and LiveView channel traffic for repetitive POSTs to storybook routes containing high-entropy parameter keys.
- Use dependency scanners such as mix deps.audit or mix hex.audit to flag phoenix_storybook versions below 1.1.0.
Monitoring Recommendations
- Alert when :erlang.system_info(:atom_count) exceeds 70% of :atom_limit on any production node.
- Monitor request rates per LiveView socket and flag sources sending unusually diverse event parameter names.
- Capture and forward erl_crash.dump artifacts to a central log store to correlate node terminations with upstream traffic patterns.
How to Mitigate CVE-2026-8469
Immediate Actions Required
- Upgrade phoenix_storybook to version 1.1.0 or later in mix.exs and redeploy.
- Remove or restrict storybook routes from production environments where they are not required.
- Place authentication and IP allow-listing in front of any /storybook routes that must remain exposed.
Patch Information
The fix is delivered in commit 96d524690af0fe197a49f60d18e564a620b9ef81 and described in GitHub Security Advisory GHSA-833p-95jq-929q. Additional details are available in the Erlang Ecosystem Foundation CNA advisory and the OSV record. The patch replaces String.to_atom/1 calls with whitelist lookups against declared attribute values and validates theme parameters before any atom conversion occurs.
Workarounds
- Disable the storybook router scope in production by guarding storybook_assets/0 and live_storybook/2 behind if Mix.env() != :prod do ... end.
- Restrict access at the reverse proxy layer (Nginx, Caddy, AWS ALB) to authenticated developer networks only.
- Increase +t BEAM emulator atom limit only as a temporary cushion; this does not prevent eventual exhaustion.
# Pin the patched version in mix.exs and fetch the update
# mix.exs
# {:phoenix_storybook, "~> 1.1.0"}
mix deps.update phoenix_storybook
mix deps.get
mix compile
# Verify the installed version
mix deps | grep phoenix_storybook
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.


