CVE-2026-67579
HighCVSS 7.4Exploitation Probability (EPSS)
Low risk32th percentile - higher than 32% of all known CVEs
Summary
Deserialization of Untrusted Data vulnerability in ash-project ash allows an unauthenticated attacker to inject a filter expression through a forged keyset pagination cursor, resulting in SQL injection or code execution depending on the data layer. Read actions with keyset pagination decode the client-supplied page[:after] or page[:before] cursor in decode_values/2 in lib/ash/page/keyset.ex using non_executable_binary_to_term/2 with [:safe]. That guard blocks new atoms, funs, and ports, but not a struct built from atoms already interned in a running Ash application, so a decoded %Ash.Query.Call{} expression survives and is spliced into the keyset filter as a comparison value in do_filters/4 and evaluated. Because the cursor bypasses the Ash.Expr macro, the runtime never applies the private?/public? gate that would otherwise reject it. On AshPostgres the injected fragment is inlined into the SQL query; on the ETS and Simple data layers it is evaluated in-process as an arbitrary function call. This issue affects ash: from 1.17.0 before 3.31.3.
Risk Assessment
Risk of SQL injection or remote code execution, potentially leading to data integrity compromise and system takeover.
Recommendation
Update the ash library to version 3.31.3 or later, which includes a fix for secure cursor deserialization.
Other vulnerabilities in ash
See all- CVE-2026-101028Medium
The ash library (versions from 2.6.0 before 3.34.6) contains an Incorrect Authorization vulnerability that allows an actor to infer data in related records they cannot read via Ash.count/2, Ash.exists/2 and Ash.aggregate/3. The aggregate path applied only the root resource's read policy, skipping related resources' read policies, enabling testing conditions against hidden rows and recovering their existence and attribute values.
- CVE-2026-94201High
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 (cast_input/2), which calls String.to_atom/1 when the attribute is configured with the unsafe_to_atom?: 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 unsafe_to_atom?: 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 version_action_name atom attribute with unsafe_to_atom?: 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 (cast_input/2, which still honors unsafe_to_atom?) is unchanged. This issue affects ash: from 3.5.1 before 3.34.3.
- CVE-2026-93477Medium
A vulnerability in the Ash library allows a user to set the value of a private action argument on the bulk destroy and bulk update paths. Arguments declared with public?: false should only be set by trusted server-side code, but the lack of a public? check on these paths enables modification from user input.
- CVE-2026-86338Medium
In Ash before version 3.33.4, field policies are documented to protect against filter-based information disclosure, but the nilling was applied only to attributes, not to calculations or aggregates. A user can reference a calculation or aggregate in a filter that they cannot see, and use the filter as an oracle to read the protected value (e.g., filter(secret_calc == "x")).
- CVE-2026-82752Medium
Improper Validation of Specified Quantity in Input vulnerability in ash-project ash allows an attacker to store a value of arbitrary size in an attribute whose length constraint should bound it. Ash measures string length with Elixir's String.length/1, which counts Unicode graphemes, in the max_length and min_length constraints of Ash.Type.String (apply_constraints/2 in lib/ash/type/string.ex), in Ash.Resource.Validation.StringLength, and in the string_length expression function. A grapheme carries an unbounded number of combining marks, so a base character followed by a million combining acute accents is one grapheme and megabytes of data, and satisfies max_length: 2. Where the data layer imposes no independent limit (ETS, Mnesia, or a Postgres text column) the whole value is persisted, so an attacker can write an entire request body into an attribute declared with a small maximum and grow storage without bound. The counting unit also disagrees with the storage layer, which counts codepoints rather than graphemes, so a value accepted by the constraint can still be rejected or truncated by the column. A Postgres varchar(n) column bounds the value itself and is not exposed. This issue affects ash: from 0.10.0 before 3.33.0.
- CVE-2026-82747Medium
Incorrect Authorization vulnerability in ash-project ash returns records that a runtime read policy denies to any actor. When a resource has an access_type :runtime read policy (a check evaluated per record rather than compiled to a filter), Ash.Policy.Authorizer decides each record in check_result/1 by discarding impossible policy scenarios and inspecting what remains. When every scenario for a record was impossible, meaning no policy can authorize it and it must be forbidden, the empty-scenario branch instead kept the record and returned it as authorized. The fix forbids a record whose scenarios are all impossible. This issue affects ash: from 3.4.44 before 3.32.2.
- CVE-2026-82749Medium
In the ash library, an Incorrect Authorization vulnerability widens a relationship's parent(...) scoping filter to match unintended records when the referenced parent field cannot be resolved. The unresolved reference defaults to nil, causing predicates like org_id == parent(org_id) to become IS NULL matches, and guards like is_nil(parent(org_id)) activate unrestricted branches, returning records that should be excluded. The fix fails the read with an error when a parent(...) reference cannot be resolved.
- CVE-2026-82748Low
Incorrect authorization vulnerability in ash authorizes an aggregate under one read action while computing it under another, so an aggregate can run with policies that do not match the action it was authorized against. This can lead to disclosure of information about records the actor cannot read.
- CVE-2026-82746Medium
In the ash library, a Missing Authorization vulnerability allows an actor to update records forbidden by resource policies through the atomic path of Ash.update_many/4. When an atomic strategy is used, the statement updates every row matched by primary key without applying policies, allowing modification of records belonging to other actors or tenants. The fix restricts the atomic path to data layers supporting changeset filters, authorizes each changeset, and merges the policy filter.
- CVE-2026-82745Medium
In the ash library, an Improper Access Control vulnerability lets a create action overwrite an existing record when the ETS or Mnesia data layer is used, because neither enforced primary-key uniqueness on insert. An actor who can set the primary key on a create can silently overwrite an existing record, destroying another entity's data without going through the update action or its policies. The fix rejects a create whose primary key already exists.
Original NVD description (English source)
Deserialization of Untrusted Data vulnerability in ash-project ash allows an unauthenticated attacker to inject a filter expression through a forged keyset pagination cursor, resulting in SQL injection or code execution depending on the data layer. Read actions with keyset pagination decode the client-supplied page[:after] or page[:before] cursor in decode_values/2 in lib/ash/page/keyset.ex using non_executable_binary_to_term/2 with [:safe]. That guard blocks new atoms, funs, and ports, but not a struct built from atoms already interned in a running Ash application, so a decoded %Ash.Query.Call{} expression survives and is spliced into the keyset filter as a comparison value in do_filters/4 and evaluated. Because the cursor bypasses the Ash.Expr macro, the runtime never applies the private?/public? gate that would otherwise reject it. On AshPostgres the injected fragment is inlined into the SQL query; on the ETS and Simple data layers it is evaluated in-process as an arbitrary function call. This issue affects ash: from 1.17.0 before 3.31.3.
Vulnerability data from NVD (NIST) · CISA KEV · EPSS

