VYPR
High severityOSV Advisory· Published Oct 10, 2025· Updated Apr 15, 2026

CVE-2025-48043

CVE-2025-48043

Description

Incorrect Authorization vulnerability in ash-project ash allows Authentication Bypass. This vulnerability is associated with program files lib/ash/policy/authorizer/authorizer.ex and program routines 'Elixir.Ash.Policy.Authorizer':strict_filters/2.

This issue affects ash: from pkg:hex/ash@0 before pkg:hex/ash@3.6.2, before 3.6.2, before 66d81300065b970da0d2f4528354835d2418c7ae.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
ashHex
< 3.6.23.6.2

Affected products

1

Patches

2
66d81300065b

Merge commit from fork

https://github.com/ash-project/ashJonatan MännchenOct 10, 2025via ghsa
4 files changed · +1095 60
  • lib/ash/policy/authorizer/authorizer.ex+58 54 modified
    @@ -1398,66 +1398,70 @@ defmodule Ash.Policy.Authorizer do
         end)
         |> Ash.Policy.SatSolver.simplify_clauses()
         |> Enum.reduce([], fn scenario, or_filters ->
    -      scenario
    -      |> Enum.map(fn
    -        {{check_module, check_opts}, true} ->
    -          result =
    -            try do
    -              nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts))
    -            rescue
    -              e ->
    -                reraise Ash.Error.to_ash_error(e, __STACKTRACE__,
    -                          bread_crumbs:
    -                            "Creating filter for check: #{check_module.describe(check_opts)} on resource: #{authorizer.resource}"
    -                        ),
    -                        __STACKTRACE__
    -            end
    +      if scenario == %{} do
    +        [false | or_filters]
    +      else
    +        scenario
    +        |> Enum.map(fn
    +          {{check_module, check_opts}, true} ->
    +            result =
    +              try do
    +                nil_to_false(check_module.auto_filter(authorizer.actor, authorizer, check_opts))
    +              rescue
    +                e ->
    +                  reraise Ash.Error.to_ash_error(e, __STACKTRACE__,
    +                            bread_crumbs:
    +                              "Creating filter for check: #{check_module.describe(check_opts)} on resource: #{authorizer.resource}"
    +                          ),
    +                          __STACKTRACE__
    +              end
     
    -          if is_nil(result) do
    -            false
    -          else
    -            result
    -          end
    +            if is_nil(result) do
    +              false
    +            else
    +              result
    +            end
     
    -        {{check_module, check_opts}, false} ->
    -          result =
    -            try do
    -              if :erlang.function_exported(check_module, :auto_filter_not, 3) do
    -                nil_to_false(
    -                  check_module.auto_filter_not(authorizer.actor, authorizer, check_opts)
    -                )
    -              else
    -                [
    -                  not:
    -                    nil_to_false(
    -                      check_module.auto_filter(authorizer.actor, authorizer, check_opts)
    -                    )
    -                ]
    +          {{check_module, check_opts}, false} ->
    +            result =
    +              try do
    +                if :erlang.function_exported(check_module, :auto_filter_not, 3) do
    +                  nil_to_false(
    +                    check_module.auto_filter_not(authorizer.actor, authorizer, check_opts)
    +                  )
    +                else
    +                  [
    +                    not:
    +                      nil_to_false(
    +                        check_module.auto_filter(authorizer.actor, authorizer, check_opts)
    +                      )
    +                  ]
    +                end
    +              rescue
    +                e ->
    +                  reraise Ash.Error.to_ash_error(e, __STACKTRACE__,
    +                            bread_crumbs:
    +                              "Creating filter for check: #{check_module.describe(check_opts)} on resource: #{authorizer.resource}"
    +                          ),
    +                          __STACKTRACE__
                   end
    -            rescue
    -              e ->
    -                reraise Ash.Error.to_ash_error(e, __STACKTRACE__,
    -                          bread_crumbs:
    -                            "Creating filter for check: #{check_module.describe(check_opts)} on resource: #{authorizer.resource}"
    -                        ),
    -                        __STACKTRACE__
    -            end
     
    -          if is_nil(result) do
    -            false
    -          else
    -            result
    -          end
    -      end)
    -      |> case do
    -        [] ->
    -          or_filters
    +            if is_nil(result) do
    +              false
    +            else
    +              result
    +            end
    +        end)
    +        |> case do
    +          [] ->
    +            or_filters
     
    -        [single] ->
    -          [single | or_filters]
    +          [single] ->
    +            [single | or_filters]
     
    -        filters ->
    -          [[and: filters] | or_filters]
    +          filters ->
    +            [[and: filters] | or_filters]
    +        end
           end
         end)
       end
    
  • lib/ash/policy/policy.ex+12 6 modified
    @@ -41,7 +41,7 @@ defmodule Ash.Policy.Policy do
             end)
             |> case do
               {:ok, scenarios} ->
    -            {:ok, scenarios, authorizer}
    +            {:ok, Enum.uniq(scenarios), authorizer}
     
               {:error, error} ->
                 {:error, authorizer, error}
    @@ -80,7 +80,7 @@ defmodule Ash.Policy.Policy do
       @doc false
       def transform(policy) do
         cond do
    -      Enum.empty?(policy.policies) ->
    +      policy.policies |> List.wrap() |> Enum.empty?() ->
             {:error, "Policies must have at least one check."}
     
           policy.bypass? &&
    @@ -96,7 +96,7 @@ defmodule Ash.Policy.Policy do
              never have an effect.
              """}
     
    -      policy.condition in [nil, []] ->
    +      policy.condition |> List.wrap() |> Enum.empty?() ->
             {:ok, %{policy | condition: [{Ash.Policy.Check.Static, result: true}]}}
     
           true ->
    @@ -267,12 +267,18 @@ defmodule Ash.Policy.Policy do
         false
       end
     
    -  defp compile_policy_expression([%struct{condition: condition, policies: policies}])
    +  defp compile_policy_expression([
    +         %struct{condition: condition, policies: policies, bypass?: bypass?}
    +       ])
            when struct in [__MODULE__, Ash.Policy.FieldPolicy] do
         condition_expression = condition_expression(condition)
         compiled_policies = compile_policy_expression(policies)
     
    -    {:or, {:and, condition_expression, compiled_policies}, {:not, condition_expression}}
    +    if bypass? do
    +      {:and, condition_expression, compiled_policies}
    +    else
    +      {:or, {:and, condition_expression, compiled_policies}, {:not, condition_expression}}
    +    end
       end
     
       defp compile_policy_expression([
    @@ -336,7 +342,7 @@ defmodule Ash.Policy.Policy do
         |> clean_constant_checks()
         |> do_debug_expr()
         |> Macro.to_string()
    -    |> then(&"#{label}: \n\n #{&1}")
    +    |> then(&"#{label}:\n\n#{&1}")
       end
     
       defp clean_constant_checks({combinator, left, right}) when combinator in [:and, :or] do
    
  • test/policy/filter_condition_test.exs+108 0 modified
    @@ -31,6 +31,90 @@ defmodule Ash.Test.Policy.FilterConditionTest do
         end
       end
     
    +  defmodule RuntimeFalsyCheck do
    +    @moduledoc false
    +    use Ash.Policy.Check
    +
    +    @impl Ash.Policy.Check
    +    def describe(_), do: "returns false at runtime"
    +
    +    @impl Ash.Policy.Check
    +    def strict_check(_, _, _), do: {:ok, :unknown}
    +
    +    @impl Ash.Policy.Check
    +    def check(_actor, _list, _map, _options), do: []
    +  end
    +
    +  defmodule FilterFalsyCheck do
    +    @moduledoc false
    +    use Ash.Policy.Check
    +
    +    @impl Ash.Policy.Check
    +    def describe(_), do: "returns false in a filter"
    +
    +    @impl Ash.Policy.Check
    +    def strict_check(_, _, _), do: {:ok, false}
    +  end
    +
    +  defmodule FinalBypassResource do
    +    @moduledoc false
    +    use Ash.Resource,
    +      domain: Ash.Test.Policy.FilterConditionTest.Domain,
    +      data_layer: Ash.DataLayer.Ets,
    +      authorizers: [Ash.Policy.Authorizer]
    +
    +    ets do
    +      private?(true)
    +    end
    +
    +    actions do
    +      default_accept :*
    +      defaults([:read, :destroy, create: :*, update: :*])
    +    end
    +
    +    attributes do
    +      uuid_primary_key :id
    +    end
    +
    +    policies do
    +      bypass always() do
    +        access_type :runtime
    +        description "Bypass never active"
    +        authorize_if FilterFalsyCheck
    +      end
    +    end
    +  end
    +
    +  defmodule RuntimeBypassResource do
    +    @moduledoc false
    +    use Ash.Resource,
    +      domain: Ash.Test.Policy.FilterConditionTest.Domain,
    +      data_layer: Ash.DataLayer.Ets,
    +      authorizers: [Ash.Policy.Authorizer]
    +
    +    ets do
    +      private?(true)
    +    end
    +
    +    actions do
    +      default_accept :*
    +      defaults([:read, :destroy, create: :*, update: :*])
    +    end
    +
    +    attributes do
    +      uuid_primary_key :id
    +    end
    +
    +    policies do
    +      default_access_type :filter
    +
    +      bypass RuntimeFalsyCheck do
    +        description "Bypass never active"
    +        authorize_if always()
    +      end
    +    end
    +  end
    +
       defmodule Domain do
         @moduledoc false
         use Ash.Domain
    @@ -41,6 +125,8 @@ defmodule Ash.Test.Policy.FilterConditionTest do
     
         resources do
           resource Resource
    +      resource RuntimeBypassResource
    +      resource FinalBypassResource
         end
       end
     
    @@ -177,4 +263,26 @@ defmodule Ash.Test.Policy.FilterConditionTest do
                  |> Ash.Changeset.for_update(:update, %{title: "title 2"}, actor: author)
                  |> Ash.update()
       end
    +
    +  test "bypass works with filter policies" do
    +    RuntimeBypassResource
    +    |> Ash.Changeset.for_create(:create, %{}, authorize?: false)
    +    |> Ash.create!()
    +
    +    assert [] =
    +             RuntimeBypassResource
    +             |> Ash.Query.for_read(:read, %{}, actor: nil, authorize?: true)
    +             |> Ash.read!()
    +  end
    +
    +  test "bypass at the end works with filter policies" do
    +    FinalBypassResource
    +    |> Ash.Changeset.for_create(:create, %{}, authorize?: false)
    +    |> Ash.create!()
    +
    +    assert [] =
    +             FinalBypassResource
    +             |> Ash.Query.for_read(:read, %{}, actor: nil, authorize?: true)
    +             |> Ash.read!()
    +  end
     end
    
  • test/policy/policy_test.exs+917 0 added
    @@ -0,0 +1,917 @@
    +defmodule Ash.Test.Policy.Policy do
    +  use ExUnit.Case, async: true
    +
    +  import Ash.SatSolver, only: [b: 1]
    +
    +  defmodule RuntimeCheck do
    +    @moduledoc false
    +    use Ash.Policy.Check
    +
    +    @impl Ash.Policy.Check
    +    def describe(_), do: "returns true at runtime"
    +
    +    @impl Ash.Policy.Check
    +    def strict_check(_, _, _), do: {:ok, :unknown}
    +
    +    @impl Ash.Policy.Check
    +    def check(_actor, list, _map, _options), do: list
    +  end
    +
    +  defmodule ErrorCheck do
    +    @moduledoc false
    +    use Ash.Policy.SimpleCheck
    +
    +    @impl Ash.Policy.Check
    +    def describe(_), do: "always errors"
    +
    +    @impl Ash.Policy.SimpleCheck
    +    def match?(_actor, _authorizer, _options), do: {:error, :something_went_wrong}
    +  end
    +
    +  describe inspect(&Ash.Policy.Policy.solve/1) do
    +    test "returns directly for runtime expandable policies" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {Ash.Policy.Check.ActorPresent, []}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true,
    +              %Ash.Policy.Authorizer{facts: %{{Ash.Policy.Check.ActorPresent, []} => true}}} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: :actor})
    +
    +      assert {:ok, false,
    +              %Ash.Policy.Authorizer{facts: %{{Ash.Policy.Check.ActorPresent, []} => false}}} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: nil})
    +    end
    +
    +    test "multiple non-bypass policies - all conditions match" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "multiple non-bypass policies - some conditions match" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: false]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "multiple non-bypass policies - no conditions match" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: nil,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "bypass policy alone - when it passes" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "bypass policy alone - when condition fails" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: nil,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "mix of bypass and regular policies - bypass succeeds and short-circuits" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "mix of bypass and regular policies - bypass fails, continues to regular" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: nil,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "mutually exclusive conditions in same policy" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {Ash.Policy.Check.ActorPresent, []},
    +              {Ash.Policy.Check.Static, [result: false]}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "forbid_if with true check forbids" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :forbid_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "forbid_if with false check does not forbid" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :forbid_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "authorize_unless with true check does not authorize" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_unless
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "authorize_unless with false check authorizes" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_unless
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "forbid_unless with true check does not forbid" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :forbid_unless
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "forbid_unless with false check forbids" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :forbid_unless
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "bypass with only forbid_if has no effect" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :forbid_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "multiple bypass policies - first succeeds" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "bypass policy after failing normal policy still fails" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.ActorPresent, []}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "mixed check types in single policy" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              },
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :forbid_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, true, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "bypass with only forbid_unless has no effect" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            bypass?: true,
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :forbid_unless
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "empty policies list" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: []
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "authorize_if with false check" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [{Ash.Policy.Check.Static, [result: true]}],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: false]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: false],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, _authorizer} = Ash.Policy.Policy.solve(authorization)
    +    end
    +
    +    test "only checks conditions that are required" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {Ash.Policy.Check.ActorPresent, []},
    +              {Ash.Policy.Check.ActorAttributeEquals, [attribute: :role, value: :admin]}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, false, %Ash.Policy.Authorizer{facts: facts}} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: nil})
    +
    +      assert %{{Ash.Policy.Check.ActorPresent, []} => false} = facts
    +
    +      refute Map.has_key?(
    +               facts,
    +               {Ash.Policy.Check.ActorAttributeEquals, [attribute: :role, value: :admin]}
    +             )
    +
    +      assert {:ok, false, %Ash.Policy.Authorizer{facts: facts}} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: %{role: :owner}})
    +
    +      assert %{{Ash.Policy.Check.ActorPresent, []} => true} = facts
    +
    +      assert %{
    +               {Ash.Policy.Check.ActorAttributeEquals, [attribute: :role, value: :admin]} => false
    +             } = facts
    +    end
    +
    +    test "declares scenarios and their requirements for uncertain output" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {RuntimeCheck, []}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {RuntimeCheck, []}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          },
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {RuntimeCheck, [some: :config]}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, required_conditions, _authorizer} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: nil})
    +
    +      assert Enum.any?(required_conditions, fn facts ->
    +               Map.get(facts, {Ash.Test.Policy.Policy.RuntimeCheck, []}) == true
    +             end)
    +
    +      assert Enum.any?(required_conditions, fn facts ->
    +               Map.get(facts, {Ash.Test.Policy.Policy.RuntimeCheck, [some: :config]}) == true
    +             end)
    +    end
    +
    +    test "returns error if check failed" do
    +      authorization = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        policies: [
    +          %Ash.Policy.Policy{
    +            condition: [
    +              {ErrorCheck, []}
    +            ],
    +            policies: [
    +              %Ash.Policy.Check{
    +                check: {Ash.Policy.Check.Static, [result: true]},
    +                check_module: Ash.Policy.Check.Static,
    +                check_opts: [result: true],
    +                type: :authorize_if
    +              }
    +            ]
    +          }
    +        ]
    +      }
    +
    +      assert {:error, _authorizer,
    +              %Ash.Error.Unknown.UnknownError{
    +                class: :unknown,
    +                error: "unknown error: :something_went_wrong"
    +              }} =
    +               Ash.Policy.Policy.solve(%{authorization | actor: nil})
    +    end
    +  end
    +
    +  describe inspect(&Ash.Policy.Policy.transform/1) do
    +    test "keeps good policy untouched" do
    +      policy = %Ash.Policy.Policy{
    +        condition: [
    +          {Ash.Policy.Check.Static, [result: false]}
    +        ],
    +        policies: [
    +          %Ash.Policy.Check{
    +            check: {Ash.Policy.Check.Static, [result: true]},
    +            check_module: Ash.Policy.Check.Static,
    +            check_opts: [result: true],
    +            type: :authorize_if
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, ^policy} = Ash.Policy.Policy.transform(policy)
    +
    +      bypass_policy = %Ash.Policy.Policy{
    +        bypass?: true,
    +        condition: [
    +          {Ash.Policy.Check.Static, [result: false]}
    +        ],
    +        policies: [
    +          %Ash.Policy.Check{
    +            check: {Ash.Policy.Check.Static, [result: true]},
    +            check_module: Ash.Policy.Check.Static,
    +            check_opts: [result: true],
    +            type: :authorize_if
    +          }
    +        ]
    +      }
    +
    +      assert {:ok, ^bypass_policy} = Ash.Policy.Policy.transform(bypass_policy)
    +    end
    +
    +    test "errors with no checks" do
    +      assert {:error, "Policies must have at least one check."} =
    +               Ash.Policy.Policy.transform(%Ash.Policy.Policy{})
    +    end
    +
    +    test "does not allow bypass with only forbid" do
    +      assert {:error, msg} =
    +               Ash.Policy.Policy.transform(%Ash.Policy.Policy{
    +                 bypass?: true,
    +                 condition: [
    +                   {Ash.Policy.Check.Static, [result: false]}
    +                 ],
    +                 policies: [
    +                   %Ash.Policy.Check{
    +                     check: {Ash.Policy.Check.Static, [result: false]},
    +                     check_module: Ash.Policy.Check.Static,
    +                     check_opts: [result: false],
    +                     type: :forbid_if
    +                   }
    +                 ]
    +               })
    +
    +      assert msg =~
    +               "This policy only contains `forbid_if` or `forbid_unless` check types therefore"
    +    end
    +
    +    test "adds empty condition if none given" do
    +      assert {:ok, %{condition: [{Ash.Policy.Check.Static, result: true}]}} =
    +               Ash.Policy.Policy.transform(%Ash.Policy.Policy{
    +                 policies: [
    +                   %Ash.Policy.Check{
    +                     check: {Ash.Policy.Check.Static, [result: true]},
    +                     check_module: Ash.Policy.Check.Static,
    +                     check_opts: [result: true],
    +                     type: :authorize_if
    +                   }
    +                 ]
    +               })
    +    end
    +  end
    +
    +  describe inspect(&Ash.Policy.Policy.fetch_or_strict_check_fact/2) do
    +    test "calculates and stores new check content" do
    +      check = {Ash.Policy.Check.ActorPresent, []}
    +
    +      authorizer = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        actor: :actor,
    +        facts: %{}
    +      }
    +
    +      assert Ash.Policy.Policy.fetch_or_strict_check_fact(authorizer, check) ==
    +               {:ok, true, %{authorizer | facts: %{check => true}}}
    +    end
    +
    +    test "gives stored fact" do
    +      check = {RuntimeCheck, []}
    +
    +      authorizer = %Ash.Policy.Authorizer{
    +        resource: Resource,
    +        action: :read,
    +        facts: %{check => false}
    +      }
    +
    +      assert Ash.Policy.Policy.fetch_or_strict_check_fact(authorizer, check) ==
    +               {:ok, false, authorizer}
    +    end
    +  end
    +
    +  describe inspect(&Ash.Policy.Policy.fetch_fact/2) do
    +    test "gives stored fact" do
    +      assert {:ok, false} =
    +               Ash.Policy.Policy.fetch_fact(%{{RuntimeCheck, []} => false}, {RuntimeCheck, []})
    +    end
    +
    +    test "ignores access_type field" do
    +      assert {:ok, false} =
    +               Ash.Policy.Policy.fetch_fact(
    +                 %{{RuntimeCheck, []} => false},
    +                 {RuntimeCheck, [access_type: :runtime]}
    +               )
    +    end
    +
    +    test "gives error if missing" do
    +      assert :error = Ash.Policy.Policy.fetch_fact(%{}, {RuntimeCheck, []})
    +    end
    +  end
    +
    +  describe inspect(&Ash.Policy.Policy.debug_expr/2) do
    +    test "generates readable expression" do
    +      assert Ash.Policy.Policy.debug_expr(
    +               b({RuntimeCheck, []} and ({RuntimeCheck, [some: :config]} or not true))
    +             ) == """
    +             Expr:
    +
    +             "returns true at runtime" and ("returns true at runtime" or not true)\
    +             """
    +    end
    +  end
    +end
    

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

7

News mentions

0

No linked articles in our index yet.