Sep 25, 2026·5 min read·18 visits
The Elixir Ash framework failed to check the 'public?' flag on action arguments when processing parameters with string keys or executing atomic updates, allowing unauthorized users to modify private parameters.
A parameter injection vulnerability exists in the Ash framework for Elixir, where untrusted string-keyed maps can bypass the 'public?: false' restriction on action arguments. An attacker can leverage this bypass to inject and overwrite private arguments, resulting in unauthorized data modification or privilege escalation depending on the target application's design.
The Ash framework is an Elixir resource-oriented application development library. Within Ash, developers can define action arguments and specify access constraints using the 'public?: false' attribute. This design ensures that certain parameters remain private, intended only for internal, trusted server-side operations (such as assigning authorization attributes or system metadata).
When constructing changeset objects from user inputs, the Ash framework is designed to sanitize external maps and discard parameters matching these private argument declarations. This establishes a security boundary separating untrusted HTTP parameter inputs from high-integrity internal variables.
However, a critical validation gap in the parameter-casting engine breaks this safety guarantee. By exploiting inconsistencies in the framework's internal argument lookup helpers, remote attackers can bypass these filtering boundaries. This results in an improperly controlled modification of dynamically-determined object attributes (CWE-915), also known as mass assignment or parameter pollution.
The core flaw stems from how Ash's internal parameter-processing engine resolves action arguments from incoming maps. The framework handles input maps containing either atom-keyed data (typically generated internally) or string-keyed data (typically parsed from JSON or URL-encoded web forms).
During regular changeset evaluation (such as executing 'for_create/3', 'for_update/3', or 'for_destroy/3' actions), parameter lookup is routed through the 'get_action_argument/2' helper function in 'lib/ash/changeset/changeset.ex'. While the atom-keyed lookup clause validated the 'public?' state of the target argument, the clause processing string-keyed inputs simply checked for string equivalence. It did not verify whether the matched argument was public.
In addition, the atomic and bulk update execution paths (such as 'fully_atomic_changeset/4') relied on the 'has_argument?/2' helper function. Prior to remediation, both the atom-keyed and string-keyed clauses in this function entirely omitted the 'public?' verification check. Consequently, any parameter matching a private argument would be accepted and applied during atomic execution, regardless of the key format.
The vulnerability was resolved by introducing strict public-visibility filters within all lookup paths inside 'lib/ash/changeset/changeset.ex'. The official patch ensures that 'public?' checks are consistently applied before returning any matched argument.
Below is the relevant portion of the codebase demonstrating the vulnerable structure and the corresponding structural changes implemented in the fix:
@@ -3520,15 +3520,15 @@ defmodule Ash.Changeset do
end
defp get_action_argument(action, name) when is_binary(name) do
- Enum.find(action.arguments, &(to_string(&1.name) == name))
+ Enum.find(action.arguments, &(&1.public? && to_string(&1.name) == name))
end
defp has_argument?(action, name) when is_atom(name) do
- Enum.any?(action.arguments, &(&1.name == name))
+ Enum.any?(action.arguments, &(&1.public? && &1.name == name))
end
defp has_argument?(action, name) when is_binary(name) do
- Enum.any?(action.arguments, &(to_string(&1.name) == name))
+ Enum.any?(action.arguments, &(&1.public? && to_string(&1.name) == name))
end
defp validate_attributes_accepted(changeset, %{accept: nil}), do: changesetBy adding the '&1.public?' validation to 'get_action_argument/2' and 'has_argument?/2', the lookup function returns nil when processing untrusted inputs that target private properties. The framework then safely discards or rejects the parameter as non-existent.
To exploit this vulnerability, an attacker must identify an Ash action that exposes public parameters alongside at least one private argument ('public?: false' or legacy 'private?: true') which controls security-critical behavior. Typical targets include fields that define tenant constraints, target ownership, or execution state.
Consider an application that uses a private argument to dictate the security context of a profile update. The developer sets up an update action where a private argument ':acting_user_id' is expected to be programmatically set by a router or controller plug. If policies rely on this private argument to authorize the modification, the logic becomes compromised.
An attacker sends an HTTP request with string-keyed fields containing the parameter they wish to overwrite. Because the web server parses incoming JSON payloads as string-keyed maps (e.g., %{"acting_user_id" => "victim_id"}), the vulnerable Ash engine maps the input to the private argument. The engine bypasses the public visibility check, binds the attacker's value, and runs the action with elevated or spoofed parameters.
This vulnerability has been assigned a CVSS v4.0 Base Score of 5.9 (Medium). The vector string indicates that while the local complexity and execution context limit direct remote code execution out-of-the-box, the impact on integrity is high (VI:H) when the vulnerability is successfully exploited.
In applications that use private arguments to enforce data isolation, tenant constraints, or authorization context, exploitation allows users to perform actions on behalf of other entities. This can lead to unauthorized data disclosure, unauthorized modification of sensitive configuration records, or a complete bypass of application-level access controls.
The overall impact depends heavily on the specific domain logic. If private arguments are not used to make security-critical decisions or enforce access control boundaries, the real-world impact may be limited to localized data corruption.
The primary remediation for this vulnerability is upgrading the 'ash' dependency to version 3.29.3 or later. This release permanently patches the parameter resolution functions and enforces proper visibility checks across both atomic and standard execution flows.
If upgrading is not immediately possible, applications can implement input-filtering middleware. Developers should sanitize incoming maps inside their Phoenix controllers or custom Plugs before passing the parameters to the Ash changeset functions. This is achieved by explicitly deleting keys that match private arguments from the parameters map.
# Temporary Plug or Controller Sanitization
def sanitize_params(conn, _opts) do
params = conn.params
cleaned_params = Map.drop(params, ["acting_user_id", "private_config_flag"])
%{conn | params: cleaned_params}
endSecurity teams should conduct code searches across their repositories to identify instances of 'public?: false' on action definitions to ensure proper awareness of potential exposure vectors.
CVSS:4.0/AV:L/AC:L/AT:P/PR:N/UI:N/VC:N/VI:H/VA:N/SC:N/SI:N/SA:N| Product | Affected Versions | Fixed Version |
|---|---|---|
ash ash-project | >= 3.0.0, < 3.29.3 | 3.29.3 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-915 |
| Attack Vector | Local |
| CVSS v4.0 | 5.9 (Medium) |
| EPSS Score | 0.00367 |
| Impact | High Integrity Violation |
| Exploit Status | poc |
| KEV Status | Not Listed |
The product receives input and dynamically determines which attributes of an object should be modified, but it does not properly control which attributes can be modified, allowing unauthorized attackers to modify sensitive attributes.
CVE-2026-16454 is an object-level authorization bypass vulnerability (CWE-284, CWE-862) in Eclipse hawkBit versions 1.0.3 and prior. The flaw exists within the Direct Device Integration (DDI) REST controller (hawkbit-ddi-resource), where endpoints responsible for enumerating software module metadata and serving firmware binaries fail to verify whether the requested software module is assigned to the authenticated device. Consequently, any device with valid credentials (such as a TargetToken or GatewayToken) can enumerate software module identifiers and download arbitrary firmware artifacts across the entire tenant namespace.
CVE-2026-62284 (GHSA-5w5g-j6hw-6p8f) is a code injection and server-side request forgery vulnerability in the console-proxy component of the OSAC Operator. The RemoteConfigResolver parses Kubernetes Secrets containing kubeconfig files via clientcmd.RESTConfigFromKubeConfig without sanitizing execution-related fields. An attacker with RBAC permissions to create or modify labeled Secrets in monitored namespaces can supply a malicious exec credential plugin, resulting in arbitrary command execution within the console-proxy pod.
CVE-2026-62361 (GHSA-xgjr-7j9q-2h4r) is an authenticated SQL injection and authorization bypass vulnerability in listmonk, an open-source, self-hosted newsletter and mailing list manager written in Go. In versions prior to 6.2.0, the subscriber export endpoint (GET /api/subscribers/export) directly interpolates user-supplied SQL filter expressions into a PostgreSQL query without invoking the table allowlist validation function (validateQueryTables). Consequently, authenticated users possessing the subscribers:sql_query and subscribers:get_all permissions can bypass relational access controls, execute arbitrary subqueries against restricted tables such as users and settings, and modify database state via writable Common Table Expressions (CTEs).
CVE-2026-28385 (GHSA-3gq2-x4qg-p4g6) is a Server-Side Request Forgery (SSRF) vulnerability in Canonical LXD versions 4.12 through 6.9. An authenticated user possessing the can_create_images entitlement can force the LXD daemon to execute arbitrary outbound HTTP and HTTPS requests via the /1.0/images API endpoint. Due to the absence of egress destination validation, an attacker can probe host loopback services, RFC 1918 private network segments, and cloud Instance Metadata Services (IMDS) from the network position of the privileged LXD daemon.
Canonical LXD and LXC Incus contain an arbitrary file write vulnerability (CWE-73 / CWE-59) in the `/1.0/instances/<name>/exec` REST API endpoint. When an instance is unpacked from an image archive, top-level symbolic links are extracted directly onto the host filesystem within the instance storage directory. If a crafted image includes a symbolic link named `exec-output` pointing to an arbitrary host path, invoking the execution endpoint with `record-output: true` causes the privileged host daemon to follow the symlink and write command output files directly to the target host directory. An authenticated attacker with permissions to import images and execute commands can exploit this flaw to write controlled content to sensitive host directories such as `/etc/cron.d`, resulting in arbitrary code execution on the host as `root`.
CVE-2026-48749 (tracked as GHSA-vghh-5rfx-xhq8 for LXD and GHSA-2q3f-q5pq-g8wv for Incus) is a critical container escape vulnerability in the Canonical LXD and Linux Containers Incus system container managers. By importing a specially crafted container image archive containing duplicate tar member headers, an authenticated attacker can replace the instance rootfs directory with a symbolic link pointing to the underlying host root filesystem. When performing file operations on a stopped container, the privileged forkfile helper binary resolves the symbolic link without confinement and chroots directly into the host filesystem, granting arbitrary root-level file read and write access.