CVE-2026-55736Medium▾ SunlitAsh: Private action arguments can be set by user input via string-keyed params and atomic changesets
▾ Sunlit zone — Low / medium · no exploitation signal
impact 27.5 · likelihood 0 · exploitation 0
Need a working PoC? Pro members can cast a request and our team develops one — it lands right here.
Exploit-prediction probability, daily snapshots since Sep 24.
Disclosure to exploitation, from the record and what we observed since indexing it.
Disclosed via GHSA
0.4%
Ash fails to consistently strip private action arguments (those declared with public?: false) when a changeset is built from an untrusted parameter map. Private arguments are meant to be set only by trusted server-side code, but a caller who controls the parameters supplied to an action can inject a value for one. Any actor able to submit parameters to an action that defines a private argument can trigger it.
Private arguments (public?: false) are meant to be populated internally (e.g. via Ash.Changeset.set_private_argument/3) and never accepted from external input. When an action is invoked with a parameter map, Ash should discard keys that name a private argument. The filtering in lib/ash/changeset/changeset.ex is incomplete, and the gap differs across the two parameter paths.
1. Regular path (for_create, for_update, for_destroy). cast_params/4 validates keys via get_action_argument/2. Its atom-keyed clause filters on public?, but the binary-keyed (string) clause does not, so a string key matching a private argument name is accepted and written into changeset.arguments. User-supplied parameter maps are string-keyed, making this the reachable case.
2. Atomic / bulk path (Ash.Changeset.fully_atomic_changeset/4). atomic_params/4 gates assignment on has_argument?/2, whose atom and binary clauses both omit the public? check, so private arguments are accepted regardless of key type.
argument :acting_user_id, :string, public?: false, and a change that writes it into an attribute.Ash.Changeset.for_create(Resource, :place, %{"item" => "book", "acting_user_id" => "victim-user-id"}).acting_user_id is present in changeset.arguments and persisted, whereas the same map with atom keys is correctly stripped.Ash.Changeset.fully_atomic_changeset(Resource, :promote, %{"acting_user_id" => "victim-user-id"}) (atom or string keys) and observe the private argument is retained either way.An attacker who can submit parameters to an action that defines a private argument can set that argument to a value of their choosing, overriding data the application intended to control server-side. Where a private argument drives authorization, identity, or record ownership (e.g. acting_user_id), this can lead to an integrity violation or privilege escalation.
ash >= 3.0.0, < 3.29.3Upgrade to a patched release:
ash 3.29.3Connected by shared product, vendor, weakness, or advisory.
CVE-2026-93477Medium· 5.9Improperly Controlled Modification of Dynamically-Determined Object Attributes vulnerability in ash-project ash allows a user to set the value of a private action argument on the bulk destroy and bulk update paths. Action arguments decl…
CVE-2026-96581High· 7.6An upsert on the primary key in Ash ignores attribute multitenancy, so one tenant can read, overwrite and take over another tenant's records
CVE-2026-114214Medium· 6.0Loading a through relationship with a limit of 1 in Ash ignores the through path and returns unrelated destination records
CVE-2026-101028Medium· 6.5Incorrect Authorization vulnerability in ash-project ash allows an actor to infer data in related records they cannot read via Ash.count/2, Ash.exists/2 and Ash.aggregate/3. Ash.Actions.Aggregate.run/4 (lib/ash/actions/aggregate.ex) app…
CVE-2026-94201High· 8.2Ash stores :atom-typed attributes as strings and compares them as strings
CVE-2026-86338Medium· 6.0Ash field_policies are documented to protect against filter-based information disclosure: when a field the actor may not see is referenced in a filter, it is replaced with an expression that evaluates to nil, so a filter cannot be used a…