CVE-2026-74837 Overview
CVE-2026-74837 is a resource exhaustion vulnerability in the ash_typescript library maintained by the ash-project. Unauthenticated attackers can exhaust the BEAM virtual machine's atom table by submitting arbitrary RPC field names, forcing the Erlang node to abort. The flaw resides in AshTypescript.FieldFormatter.convert_to_field_atom/2, which mints new atoms from client-supplied strings without any allowlist, length limit, or rate control. Because atoms are never garbage collected in the BEAM VM, each unique field name permanently consumes a slot in the atom table until the node crashes. Field names longer than 255 characters trigger an uncaught SystemLimitError. The issue affects ash_typescript versions 0.1.0 through 0.17.x and is fixed in 0.18.0 [CWE-770].
Critical Impact
An unauthenticated remote attacker can crash the entire Erlang node hosting an application that uses ash_typescript RPC, resulting in denial of service for all tenants on that node.
Affected Products
- ash-project ash_typescript versions 0.1.0 through 0.17.x
- Elixir/Phoenix applications exposing RPC endpoints via AshTypescript.Rpc
- BEAM VM nodes hosting vulnerable ash_typescript deployments
Discovery Timeline
- 2026-09-01 - CVE-2026-74837 published to NVD
- 2026-09-01 - Last updated in NVD database
Technical Details for CVE-2026-74837
Vulnerability Analysis
The vulnerability originates in lib/ash_typescript/field_formatter.ex. The function convert_to_field_atom/2 first delegates to parse_input_field/2, which attempts to resolve the client-supplied name using String.to_existing_atom/1. When no matching atom exists, parse_input_field/2 falls back to returning the raw string. convert_to_field_atom/2 then calls String.to_atom/1 on that string, minting a new permanent atom.
RPC field selection reaches this code path for every field name in a request through AshTypescript.Rpc.FieldProcessing.FieldSelector. The selector resolves each name to an atom before checking whether the field actually exists on the resource. No allowlist, length bound, or rate limit intervenes.
Root Cause
The root cause is unbounded atom creation from untrusted input. The BEAM VM enforces a hard atom table limit (default 1,048,576 entries) and never garbage collects atoms. Each distinct string passed to String.to_atom/1 consumes a permanent slot. Additionally, String.to_atom/1 raises SystemLimitError for strings exceeding 255 bytes, providing a secondary crash primitive with a single request.
Attack Vector
An unauthenticated attacker sends RPC requests containing large numbers of unique, randomized field names. Each unique name mints a new atom. Repeated requests progressively fill the atom table until the VM aborts. Alternatively, a single request containing a field name longer than 255 characters triggers an immediate uncaught SystemLimitError.
// Security patch in lib/ash_typescript/field_formatter.ex
end
@doc """
- Converts a field name to an atom, applying the formatter for case conversion.
+ Resolves a field name to its existing atom, applying the formatter for case conversion.
- Unlike `parse_input_field/2` which tries to use existing atoms, this function
- always creates an atom (using String.to_atom/1 for strings that aren't existing atoms).
- Use this when you need guaranteed atom output for field selection.
+ Atoms are passed through unchanged. Every valid string field name corresponds to
+ an atom that already exists (resource attributes, relationships, calculations, and
+ aggregates are all defined at compile time), so a string resolves to an existing
+ atom when one is available and otherwise returns the formatted string unchanged.
+ Downstream field selection compares the result against the known field atoms, so an
+ unresolved name simply fails as an unknown field.
+
+ This deliberately never calls `String.to_atom/1`: client-supplied field names
+ are attacker-controllable, and minting a fresh atom per name would allow atom
+ table exhaustion (a node-wide denial of service).
Source: GitHub Commit df95df4
Detection Methods for CVE-2026-74837
Indicators of Compromise
- Rapid growth in the BEAM atom table count reported by :erlang.system_info(:atom_count) on affected nodes.
- RPC requests containing large volumes of unique, high-entropy or randomized field names not matching any known resource schema.
- Node crashes with log entries referencing atom table exhaustion or uncaught SystemLimitError originating from String.to_atom/1.
Detection Strategies
- Instrument the RPC entry point to sample and log the cardinality of field names per request and per client IP.
- Monitor :erlang.memory(:atom) and :erlang.system_info(:atom_count) and alert when values approach the configured atom table limit.
- Correlate application crash telemetry with recent inbound requests to identify malicious field-name patterns.
Monitoring Recommendations
- Enable structured logging on all AshTypescript.Rpc endpoints and forward events to a centralized analytics platform.
- Configure alerting on abnormal request volumes to RPC endpoints, particularly requests with unusually long field names.
- Track BEAM VM health metrics (atom count, process count, memory) as part of standard application observability.
How to Mitigate CVE-2026-74837
Immediate Actions Required
- Upgrade ash_typescript to version 0.18.0 or later, which replaces convert_to_field_atom/2 with resolve_field_name/2 and eliminates the String.to_atom/1 call path.
- Audit any custom callers of AshTypescript.FieldFormatter.convert_to_field_atom/2 in your codebase and migrate them to the new resolver.
- Restart affected BEAM nodes after upgrading to reset the atom table to a healthy baseline.
Patch Information
The fix is delivered in commit df95df4b9afdca5e5bbce32dbd566ccc49a7f14b and shipped in ash_typescript0.18.0. Downstream field selection now compares the resolved value against known compile-time atoms, so unresolved names fail as unknown fields rather than allocating new atoms. See the GitHub Security Advisory GHSA-mhxc-mhqx-3v28 and the CNA advisory from the Erlang Ecosystem Foundation for full details.
Workarounds
- Place a reverse proxy or web application firewall in front of RPC endpoints to enforce request size limits and reject field names longer than 255 characters.
- Apply rate limiting per client IP or authenticated principal on RPC routes to slow atom table growth prior to patching.
- Increase the BEAM atom table limit via the +t VM flag as a temporary buffer, understanding this only delays exhaustion.
# Configuration example: enforce rate limiting and payload size on the RPC route
# (Phoenix Endpoint example)
plug Plug.Parsers,
parsers: [:json],
pass: ["application/json"],
json_decoder: Jason,
length: 100_000
# In your router, apply a rate-limit plug (e.g., Hammer) to RPC routes
pipeline :rpc do
plug :accepts, ["json"]
plug MyApp.RateLimiter, max_requests: 60, interval_seconds: 60
end
scope "/rpc", MyAppWeb do
pipe_through :rpc
post "/", RpcController, :handle
end
Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

