VYPR
Medium severity5.3NVD Advisory· Published Apr 15, 2025· Updated Apr 15, 2026

CVE-2025-32782

CVE-2025-32782

Description

Ash Authentication provides authentication for the Ash framework. The confirmation flow for account creation currently uses a GET request triggered by clicking a link sent via email. Some email clients and security tools (e.g., Outlook, virus scanners, and email previewers) may automatically follow these links, unintentionally confirming the account. This allows an attacker to register an account using another user’s email and potentially have it auto-confirmed by the victim’s email client. This does not allow attackers to take over or access existing accounts or private data. It is limited to account confirmation of new accounts only. This vulnerability is fixed in 4.7.0.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ash_authenticationHex
< 4.7.04.7.0

Patches

2
99ea38977fd4

improvement: mitigate medium-sev security issue for confirmation emails (#968)

14 files changed · +120 11
  • documentation/dsls/DSL-AshAuthentication.AddOn.Confirmation.md+18 0 modified
    @@ -70,6 +70,23 @@ so via the `AshAuthentication.Strategy` protocol.
         ...> user.confirmed_at >= one_second_ago()
         true
     
    +## Usage with AshAuthenticationPhoenix
    +
    +If you are using `AshAuthenticationPhoenix`, and have `require_interaction?` set to `true`,
    +which you very much should, then you will need to add a `confirm_route` to your router. This
    +is placed in the same location as `auth_routes`, and should be provided the user and the
    +strategy name. For example:
    +
    +```elixir
    +# Remove this if you do not want to use the confirmation strategy
    +confirm_route(
    +  MyApp.Accounts.User,
    +  :confirm_new_user,
    +  auth_routes_prefix: "/auth",
    +  overrides: [MyApp.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
    +)
    +```
    +
     ## Plugs
     
     Confirmation provides a single endpoint for the `:confirm` phase.  If you wish
    @@ -112,6 +129,7 @@ User confirmation flow
     | [`sender`](#authentication-add_ons-confirmation-sender){: #authentication-add_ons-confirmation-sender .spark-required} | `(any, any, any -> any) \| module` |  | How to send the confirmation instructions to the user. |
     | [`token_lifetime`](#authentication-add_ons-confirmation-token_lifetime){: #authentication-add_ons-confirmation-token_lifetime } | `pos_integer \| {pos_integer, :days \| :hours \| :minutes \| :seconds}` | `{3, :days}` | How long should the confirmation token be valid.  If no unit is provided, then hours is assumed. |
     | [`prevent_hijacking?`](#authentication-add_ons-confirmation-prevent_hijacking?){: #authentication-add_ons-confirmation-prevent_hijacking? } | `boolean` | `true` | Whether or not to prevent upserts over unconfirmed uers. See [the confirmation guide](/documentation/topics/confirmation.md) for more. |
    +| [`require_interaction?`](#authentication-add_ons-confirmation-require_interaction?){: #authentication-add_ons-confirmation-require_interaction? } | `boolean` | `false` | Whether or not to require user interaction to confirm. If true, the confirmation URLs are changed to a `POST` request, and AshAuthenticationPhoenix will show a button to confirm when the page is visited |
     | [`confirmed_at_field`](#authentication-add_ons-confirmation-confirmed_at_field){: #authentication-add_ons-confirmation-confirmed_at_field } | `atom` | `:confirmed_at` | The name of the field to store the time that the last confirmation took place. Created if it does not exist. |
     | [`confirm_on_create?`](#authentication-add_ons-confirmation-confirm_on_create?){: #authentication-add_ons-confirmation-confirm_on_create? } | `boolean` | `true` | Generate and send a confirmation token when a new resource is created. Triggers when a create action is executed _and_ one of the monitored fields is being set. |
     | [`confirm_on_update?`](#authentication-add_ons-confirmation-confirm_on_update?){: #authentication-add_ons-confirmation-confirm_on_update? } | `boolean` | `true` | Generate and send a confirmation token when a resource is changed.  Triggers when an update action is executed _and_ one of the monitored fields is being set. |
    
  • documentation/tutorials/confirmation.md+2 0 modified
    @@ -97,6 +97,7 @@ defmodule MyApp.Accounts.User do
             monitor_fields [:email]
             confirm_on_create? true
             confirm_on_update? false
    +        require_interaction? true
             sender MyApp.Accounts.User.Senders.SendNewUserConfirmationEmail
           end
         end
    @@ -221,6 +222,7 @@ defmodule MyApp.Accounts.User do
             confirm_on_create? false
             confirm_on_update? true
             confirm_action_name :confirm_change
    +        require_interaction? true
             sender MyApp.Accounts.User.Senders.SendEmailChangeConfirmationEmail
           end
         end
    
  • .formatter.exs+1 0 modified
    @@ -93,6 +93,7 @@ spark_locals_without_parens = [
       request_action_name: 1,
       request_password_reset_action_name: 1,
       require_confirmed_with: 1,
    +  require_interaction?: 1,
       require_token_presence_for_authentication?: 1,
       resettable: 0,
       resettable: 1,
    
  • lib/ash_authentication/add_ons/confirmation/dsl.ex+6 0 modified
    @@ -53,6 +53,12 @@ defmodule AshAuthentication.AddOn.Confirmation.Dsl do
               doc:
                 "Whether or not to prevent upserts over unconfirmed uers. See [the confirmation guide](/documentation/topics/confirmation.md) for more."
             ],
    +        require_interaction?: [
    +          type: :boolean,
    +          default: false,
    +          doc:
    +            "Whether or not to require user interaction to confirm. If true, the confirmation URLs are changed to a `POST` request, and AshAuthenticationPhoenix will show a button to confirm when the page is visited"
    +        ],
             confirmed_at_field: [
               type: :atom,
               doc:
    
  • lib/ash_authentication/add_ons/confirmation.ex+18 0 modified
    @@ -69,6 +69,23 @@ defmodule AshAuthentication.AddOn.Confirmation do
           ...> user.confirmed_at >= one_second_ago()
           true
     
    +  ## Usage with AshAuthenticationPhoenix
    +
    +  If you are using `AshAuthenticationPhoenix`, and have `require_interaction?` set to `true`,
    +  which you very much should, then you will need to add a `confirm_route` to your router. This
    +  is placed in the same location as `auth_routes`, and should be provided the user and the
    +  strategy name. For example:
    +
    +  ```elixir
    +  # Remove this if you do not want to use the confirmation strategy
    +  confirm_route(
    +    MyApp.Accounts.User,
    +    :confirm_new_user,
    +    auth_routes_prefix: "/auth",
    +    overrides: [MyApp.AuthOverrides, AshAuthentication.Phoenix.Overrides.Default]
    +  )
    +  ```
    +
       ## Plugs
     
       Confirmation provides a single endpoint for the `:confirm` phase.  If you wish
    @@ -90,6 +107,7 @@ defmodule AshAuthentication.AddOn.Confirmation do
                 confirm_on_update?: true,
                 prevent_hijacking?: true,
                 confirmed_at_field: :confirmed_at,
    +            require_interaction?: false,
                 inhibit_updates?: true,
                 monitor_fields: [],
                 auto_confirm_actions: [],
    
  • lib/ash_authentication/add_ons/confirmation/plug.ex+27 3 modified
    @@ -3,7 +3,8 @@ defmodule AshAuthentication.AddOn.Confirmation.Plug do
       Handlers for incoming OAuth2 HTTP requests.
       """
     
    -  alias AshAuthentication.{AddOn.Confirmation, Strategy}
    +  alias AshAuthentication.{AddOn.Confirmation, Info, Strategy}
    +  alias AshAuthentication.Errors.InvalidToken
       alias Plug.Conn
       import AshAuthentication.Plug.Helpers, only: [store_authentication_result: 2]
       import Ash.PlugHelpers, only: [get_actor: 1, get_tenant: 1, get_context: 1]
    @@ -16,8 +17,14 @@ defmodule AshAuthentication.AddOn.Confirmation.Plug do
         opts = opts(conn)
     
         result =
    -      strategy
    -      |> Strategy.action(:confirm, conn.params, opts)
    +      case params(strategy, conn.params) do
    +        {:ok, params} ->
    +          strategy
    +          |> Strategy.action(:confirm, params, opts)
    +
    +        :error ->
    +          {:error, InvalidToken.exception(type: :confirmation)}
    +      end
     
         conn
         |> store_authentication_result(result)
    @@ -27,4 +34,21 @@ defmodule AshAuthentication.AddOn.Confirmation.Plug do
         [actor: get_actor(conn), tenant: get_tenant(conn), context: get_context(conn) || %{}]
         |> Enum.reject(&is_nil(elem(&1, 1)))
       end
    +
    +  defp params(strategy, params) when strategy.require_interaction? do
    +    case params do
    +      %{"confirm" => _} ->
    +        {:ok, params}
    +
    +      params ->
    +        strategy.resource
    +        |> Info.authentication_subject_name!()
    +        |> to_string()
    +        |> then(&Map.fetch(params, &1))
    +    end
    +  end
    +
    +  defp params(_strategy, params) do
    +    {:ok, params}
    +  end
     end
    
  • lib/ash_authentication/add_ons/confirmation/strategy.ex+3 1 modified
    @@ -28,7 +28,9 @@ defimpl AshAuthentication.Strategy, for: AshAuthentication.AddOn.Confirmation do
     
       @doc false
       @spec method_for_phase(Confirmation.t(), phase) :: Strategy.http_method()
    -  def method_for_phase(_, _), do: :get
    +  def method_for_phase(strategy, _phase) when strategy.require_interaction?, do: :post
    +
    +  def method_for_phase(_strategy, _phase), do: :get
     
       @doc false
       @spec routes(Confirmation.t()) :: [Strategy.route()]
    
  • lib/ash_authentication/add_ons/confirmation/transformer.ex+37 0 modified
    @@ -32,6 +32,7 @@ defmodule AshAuthentication.AddOn.Confirmation.Transformer do
                  strategy.confirm_action_name,
                  &build_confirm_action(&1, strategy)
                ),
    +         :ok <- warn_on_require_interaction(strategy),
              :ok <- validate_confirm_action(dsl_state, strategy),
              {:ok, dsl_state} <-
                maybe_build_attribute(
    @@ -71,6 +72,42 @@ defmodule AshAuthentication.AddOn.Confirmation.Transformer do
         end
       end
     
    +  defp warn_on_require_interaction(strategy) when strategy.require_interaction? do
    +    :ok
    +  end
    +
    +  defp warn_on_require_interaction(strategy) do
    +    bypassing_error? =
    +      Application.get_env(
    +        :ash_authentication_phoenix,
    +        :bypass_require_interaction_for_confirmation?,
    +        false
    +      )
    +
    +    if bypassing_error? do
    +      :ok
    +    else
    +      {:error,
    +       DslError.exception(
    +         path: [:authentication, :add_ons, :confirmation],
    +         message: """
    +         `require_interaction?` must be set to true on the
    +         #{inspect(strategy.name)} confirmation for #{inspect(strategy.resource)}.
    +         Without it, confirmation links use a `GET` endpoint for confirmation. Some
    +         email clients and security tools (e.g., Outlook, virus scanners, and email
    +         previewers) may automatically follow these links, unintentionally
    +         confirming the account. This allows an attacker to register an account
    +         using another user’s email and potentially have it auto-confirmed by the
    +         victim’s email client.
    +
    +         See the security advisory for more information:
    +
    +         https://github.com/team-alembic/ash_authentication/security/advisories/GHSA-3988-q8q7-p787
    +         """
    +       )}
    +    end
    +  end
    +
       defp validate_monitor_fields(_dsl_state, %{monitor_fields: []}),
         do:
           {:error,
    
  • lib/mix/tasks/ash_authentication.add_strategy.ex+1 0 modified
    @@ -318,6 +318,7 @@ if Code.ensure_loaded?(Igniter) do
                 monitor_fields [:email]
                 confirm_on_create? true
                 confirm_on_update? false
    +            require_interaction? true
                 confirmed_at_field :confirmed_at
                 auto_confirm_actions [:sign_in_with_magic_link, :reset_password_with_token]
                 sender #{inspect(sender)}
    
  • test/ash_authentication/add_ons/confirmation/plug_test.exs+3 3 modified
    @@ -31,7 +31,7 @@ defmodule AshAuthentication.AddOn.Confirmation.PlugTest do
           }
     
           assert {_conn, {:error, error}} =
    -               :get
    +               :post
                    |> conn("/", params)
                    |> Plug.confirm(strategy)
                    |> Helpers.get_authentication_result()
    @@ -47,7 +47,7 @@ defmodule AshAuthentication.AddOn.Confirmation.PlugTest do
           }
     
           assert {_conn, {:error, error}} =
    -               :get
    +               :post
                    |> conn("/", params)
                    |> Plug.confirm(strategy)
                    |> Helpers.get_authentication_result()
    @@ -73,7 +73,7 @@ defmodule AshAuthentication.AddOn.Confirmation.PlugTest do
           }
     
           assert {_conn, {:ok, confirmed_user}} =
    -               :get
    +               :post
                    |> conn("/", params)
                    |> Plug.confirm(strategy)
                    |> Helpers.get_authentication_result()
    
  • test/support/example_multi_tenant/user.ex+1 0 modified
    @@ -152,6 +152,7 @@ defmodule ExampleMultiTenant.User do
           confirmation :confirm do
             monitor_fields [:username]
             inhibit_updates? true
    +        require_interaction? true
     
             sender fn _user, token, opts ->
               username =
    
  • test/support/example_multi_tenant/user_with_register_magic_link.ex+1 2 modified
    @@ -57,8 +57,7 @@ defmodule ExampleMultiTenant.UserWithRegisterMagicLink do
             inhibit_updates? false
             confirm_on_create? true
             confirm_on_update? true
    -        # in a real setup, you should have this
    -        # but we don't for testing purposes
    +        require_interaction? true
             auto_confirm_actions [:sign_in_with_magic_link]
     
             sender fn user, _, _ ->
    
  • test/support/example/user.ex+1 0 modified
    @@ -179,6 +179,7 @@ defmodule Example.User do
           confirmation :confirm do
             monitor_fields [:username]
             inhibit_updates? true
    +        require_interaction? true
     
             sender fn _user, token, opts ->
               username =
    
  • test/support/example/user_with_register_magic_link.ex+1 2 modified
    @@ -55,8 +55,7 @@ defmodule Example.UserWithRegisterMagicLink do
             inhibit_updates? false
             confirm_on_create? true
             confirm_on_update? true
    -        # in a real setup, you should have this
    -        # but we don't for testing purposes
    +        require_interaction? true
             auto_confirm_actions [:sign_in_with_magic_link]
     
             sender fn user, _, _ ->
    

Vulnerability mechanics

Generated by null/stub on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

4

News mentions

0

No linked articles in our index yet.