From ab0407abb125535a9aa913b5eb3c5e0f63ce7fe0 Mon Sep 17 00:00:00 2001 From: Moritz Date: Thu, 22 Jan 2026 22:37:04 +0100 Subject: [PATCH] Replace NoActor runtime Mix.env with compile-time config Use Application.compile_env for release-safety. Config only set in test.exs (defaults to false). --- config/test.exs | 4 ++ lib/mv/authorization/checks/no_actor.ex | 17 +++---- .../mv/authorization/checks/no_actor_test.exs | 47 +++++++------------ 3 files changed, 26 insertions(+), 42 deletions(-) diff --git a/config/test.exs b/config/test.exs index 45acaa4..b48c408 100644 --- a/config/test.exs +++ b/config/test.exs @@ -49,3 +49,7 @@ config :mv, :require_token_presence_for_authentication, false # Enable SQL Sandbox for async LiveView tests # This flag controls sync vs async behavior in CycleGenerator after_action hooks config :mv, :sql_sandbox, true + +# Allow operations without actor in test environment (NoActor check) +# SECURITY: This must ONLY be true in test.exs, never in prod/dev +config :mv, :allow_no_actor_bypass, true diff --git a/lib/mv/authorization/checks/no_actor.ex b/lib/mv/authorization/checks/no_actor.ex index ffb4a9e..1c4946f 100644 --- a/lib/mv/authorization/checks/no_actor.ex +++ b/lib/mv/authorization/checks/no_actor.ex @@ -42,9 +42,9 @@ defmodule Mv.Authorization.Checks.NoActor do use Ash.Policy.SimpleCheck # Compile-time check: Only allow no-actor bypass in test environment - @allow_no_actor_bypass Mix.env() == :test - # Alternative (if you want to control via config): - # @allow_no_actor_bypass Application.compile_env(:mv, :allow_no_actor_bypass, false) + # SECURITY: This must ONLY be true in test.exs, never in prod/dev + # Using compile_env instead of Mix.env() for release-safety + @allow_no_actor_bypass Application.compile_env(:mv, :allow_no_actor_bypass, false) @impl true def describe(_opts) do @@ -58,14 +58,9 @@ defmodule Mv.Authorization.Checks.NoActor do @impl true def match?(nil, _context, _opts) do # Actor is nil - # Double-check: compile-time AND runtime environment - if @allow_no_actor_bypass and Mix.env() == :test do - # Test environment: Allow all operations - true - else - # Production/dev: Deny all operations (fail-closed for security) - false - end + # SECURITY: Only allow if compile_env flag is set (test.exs only) + # No runtime Mix.env() check - fail-closed by default (false) + @allow_no_actor_bypass end def match?(_actor, _context, _opts) do diff --git a/test/mv/authorization/checks/no_actor_test.exs b/test/mv/authorization/checks/no_actor_test.exs index 07efa0a..35205a6 100644 --- a/test/mv/authorization/checks/no_actor_test.exs +++ b/test/mv/authorization/checks/no_actor_test.exs @@ -11,7 +11,7 @@ defmodule Mv.Authorization.Checks.NoActorTest do describe "match?/3" do test "returns true when actor is nil in test environment" do - # In test environment, NoActor should allow operations + # In test environment (config :allow_no_actor_bypass = true), NoActor allows operations result = NoActor.match?(nil, %{}, []) assert result == true end @@ -22,46 +22,31 @@ defmodule Mv.Authorization.Checks.NoActorTest do assert result == false end - test "has compile-time guard preventing production use" do - # The @allow_no_actor_bypass module attribute is set at compile time - # In test: true, in prod/dev: false - # This test verifies the guard exists (compile-time check) - # Runtime check is verified by the fact that match? checks Mix.env() + test "uses compile-time config (not runtime Mix.env)" do + # The @allow_no_actor_bypass is set via Application.compile_env at compile time + # In test.exs: config :mv, :allow_no_actor_bypass, true + # In prod/dev: not set (defaults to false) + # This ensures the check is release-safe (no runtime Mix.env dependency) result = NoActor.match?(nil, %{}, []) - # In test environment, should allow - if Mix.env() == :test do - assert result == true - else - # In other environments, should deny - assert result == false - end - end + # In test environment (as compiled), should allow + assert result == true - test "has runtime guard preventing production use" do - # The match? function checks Mix.env() at runtime - # This provides defense in depth against config drift - result = NoActor.match?(nil, %{}, []) - - # Should match compile-time guard - if Mix.env() == :test do - assert result == true - else - assert result == false - end + # Note: We cannot test "production mode" here because the flag is compile-time. + # Production safety is guaranteed by: + # 1. Config only set in test.exs + # 2. Default is false (fail-closed) + # 3. No runtime environment checks end end describe "describe/1" do - test "returns description based on environment" do + test "returns description based on compile-time config" do description = NoActor.describe([]) assert is_binary(description) - if Mix.env() == :test do - assert description =~ "test environment" - else - assert description =~ "production/dev" - end + # In test environment (compiled with :allow_no_actor_bypass = true) + assert description =~ "test environment" end end end