Skip to main content
CVE Vulnerability Database
Vulnerability Database/CVE-2026-55736

CVE-2026-55736: Ash-Project Privilege Escalation Flaw

CVE-2026-55736 is a privilege escalation vulnerability in ash-project ash that allows attackers to modify private action arguments intended for server-side control only. This article covers technical details, affected versions, and mitigations.

Published:

CVE-2026-55736 Overview

CVE-2026-55736 is an improperly controlled modification of dynamically-determined object attributes vulnerability [CWE-915] in the ash-project ash framework for Elixir. The flaw lets a user set the value of a private action argument that should only be controlled by trusted server-side code. Action arguments declared with public?: false are intended for internal use through APIs such as Ash.Changeset.set_private_argument/3. The framework's filtering logic fails to strip these private arguments when user-supplied parameter keys are binaries (strings) rather than atoms. The issue affects ash versions from 3.0.0 before 3.29.3.

Critical Impact

An attacker submitting parameters to an action that defines a private argument can inject arbitrary values, potentially driving authorization decisions or record ownership and resulting in integrity violation or privilege escalation.

Affected Products

  • ash-project ash versions 3.0.0 through 3.29.2
  • Elixir applications using Ash.Changeset create, update, or destroy actions with private arguments
  • Elixir applications using Ash.Changeset.fully_atomic_changeset/4 including atomic and bulk update paths

Discovery Timeline

  • 2026-06-23 - CVE CVE-2026-55736 published to NVD
  • 2026-06-23 - Last updated in NVD database

Technical Details for CVE-2026-55736

Vulnerability Analysis

The ash framework distinguishes public from private action arguments using the public? flag. Public arguments accept user input through parameter maps, while private arguments must only be assigned through trusted server-side code such as Ash.Changeset.set_private_argument/3. When a changeset is constructed from a parameter map, the framework attempts to filter private arguments out of user input. The filtering implementation is incomplete and creates two distinct code paths that handle private arguments differently.

In the regular changeset path used by for_create, for_update, and for_destroy, private arguments are stripped only when the parameter key is an atom. User-supplied parameters from HTTP requests are typically binaries (strings), so the private argument check is bypassed entirely. In the atomic path reached through Ash.Changeset.fully_atomic_changeset/4, atomic actions, and bulk updates, private arguments are not stripped regardless of key type.

Root Cause

The root cause lies in the helper functions get_action_argument/2 and has_argument?/2 in lib/ash/changeset/changeset.ex. These functions matched arguments by name without verifying the public? flag, allowing private arguments to be treated identically to public ones during parameter casting.

Attack Vector

An attacker who can submit parameters to an action that defines a private argument can include that argument name as a string key in the parameter map. If the application uses the private argument for sensitive logic, such as an acting_user_id driving authorization or ownership assignment, the attacker can override the server-controlled value. This can result in privilege escalation or unauthorized modification of records belonging to other users.

text
   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

Source: GitHub Commit Fix. The patch adds an explicit &1.public? check to all three argument lookup helpers, ensuring private arguments are excluded when matching parameters from user input.

Detection Methods for CVE-2026-55736

Indicators of Compromise

  • Unexpected values appearing in audit logs for fields populated from private arguments such as acting_user_id, tenant identifiers, or ownership keys.
  • Action invocations where parameter maps contain string keys matching the names of arguments declared with public?: false.
  • Records created or updated with ownership or authorization context inconsistent with the authenticated session.

Detection Strategies

  • Audit Ash resource definitions for arguments declared with public?: false and review every action that references them through change set_attribute, change set_context, or custom changes.
  • Inspect application logs and HTTP request bodies for parameter keys that match private argument names.
  • Add server-side assertions after parameter casting to confirm private arguments equal the trusted value set via Ash.Changeset.set_private_argument/3.

Monitoring Recommendations

  • Enable changeset logging in development and staging to capture the full argument map applied to each action and alert on deviations.
  • Correlate authentication context with record ownership changes through SIEM rules targeting ash action telemetry events.
  • Track dependency manifests (mix.lock) across deployed services to identify hosts still running ash versions earlier than 3.29.3.

How to Mitigate CVE-2026-55736

Immediate Actions Required

  • Upgrade ash to version 3.29.3 or later in all mix.exs dependency declarations and redeploy affected services.
  • Inventory every Ash action that declares a private argument and audit logs for evidence of parameter injection prior to the upgrade.
  • Revoke and reissue any credentials or session tokens that may have been used to manipulate ownership-related private arguments.

Patch Information

The issue is fixed in ash 3.29.3. The corrective commit d9b3100219b3ea86d73202bf7368c03a7688efea adds the public? check to argument resolution helpers. See the GitHub Security Advisory GHSA-f4hc-ppw9-4hhw and the CNA CVE-2026-55736 Detail for advisory text and version metadata.

Workarounds

  • If upgrading immediately is not possible, explicitly delete known private argument keys from parameter maps before invoking for_create, for_update, for_destroy, or atomic and bulk update functions.
  • Re-assert the trusted value of private arguments using Ash.Changeset.set_private_argument/3 after parameter casting to overwrite any injected value.
  • Restrict access to actions that depend on private arguments to authenticated, authorization-checked endpoints until the patch is applied.
bash
# Update ash dependency in mix.exs to the patched version
# {:ash, "~> 3.29.3"}
mix deps.update ash
mix deps.get
mix compile --force

Disclaimer: This content was generated using AI. While we strive for accuracy, please verify critical information with official sources.

Default Legacy - Prefooter | Experience the World’s Most Advanced Cybersecurity Platform

Experience the Most Advanced Cybersecurity Platform

See how the world’s most intelligent, autonomous cybersecurity platform can protect your organization today and into the future.