CVE-2026-94201: Filtering an :atom attribute with unsafe_to_atom? can exhaust the BEAM atom table in Ash
Ash stores :atom-typed attributes as strings and compares them as strings. When such an attribute is referenced in a filter, the comparison value is coerced through Ash.Type.Atom. Because the type defined no coerce/2 callback, coercion fell back to the default (castinput/2), which calls String.toatom/1 when the attribute is configured with the unsafetoatom?: true constraint.
Filtering such an attribute with attacker-controlled strings therefore interned a new, permanent atom for every distinct value. Atoms are never garbage collected and the BEAM caps the atom table, so an actor who can supply filter values for a public, filterable :atom attribute declared with unsafetoatom?: true can exhaust the atom table and crash the node (denial of service). AshPaperTrail is a notable example: its version resources expose a public, filterable versionactionname atom attribute with unsafetoatom?: true by default.
The fix adds a coerce/2 to Ash.Type.Atom that never interns atoms — a comparison value is left as a string, since the type is stored and compared as a string. Setting the attribute from action input (castinput/2, which still honors unsafetoatom?) is unchanged.
This issue affects ash: from 3.5.1 before 3.34.3.
Affected Software
Remediation
Recommended actions to resolve this vulnerability, in priority order.
- Upgrade
Upgrade
ashto a version that resolves this vulnerability.Fixed in 3.34.3
Event History
Frequently Asked Questions
Which deployments are exposed to this issue?
Exposure requires a public, filterable attribute typed as :atom with the unsafe_to_atom?: true constraint, where an attacker can supply distinct filter values. Such inputs can permanently consume BEAM atoms until the node crashes.
Is AshPaperTrail affected by default?
AshPaperTrail version resources expose a public, filterable version_action_name atom attribute with unsafe_to_atom?: true by default. This makes it a notable affected configuration when untrusted actors can provide filter values.
How can I identify risky attributes in an application?
Review :atom-typed attributes for the combination of unsafe_to_atom?: true and public filterability. Attributes that are not reachable through attacker-controlled filtering do not meet the described exploitation conditions.
What behavior changes with the fix?
Filter comparison values for :atom attributes are left as strings rather than being interned as atoms, matching how these values are stored and compared. Setting an attribute through action input is unchanged: cast_input/2 still honors unsafe_to_atom?.