""" Test: Authentication Fail-Closed Ensures unknown tools, profiles, and scopes are denied. Authority must never be granted by default. """ import pytest from vaultmesh_mcp.tools.auth import ( auth_check_permission, auth_create_dev_session, Profile, check_profile_permission, get_profile_for_scope, SCOPE_TOOLS, ) class TestFailClosed: """Fail-closed semantics - deny by default.""" def test_unknown_tool_denied(self): """Unknown tool must be denied regardless of scope.""" session = auth_create_dev_session(scope="sovereign") token = session["token"] result = auth_check_permission(token, "unknown_tool_xyz") assert not result["allowed"], "Unknown tool should be denied" def test_unknown_scope_maps_to_observer(self): """Unknown scope must map to OBSERVER (most restrictive).""" profile = get_profile_for_scope("unknown_scope_xyz") assert profile == Profile.OBSERVER, ( f"Unknown scope should map to OBSERVER, got {profile}" ) def test_invalid_token_denied(self): """Invalid token must be denied.""" result = auth_check_permission("invalid_token_xyz", "cognitive_context") assert not result["allowed"], "Invalid token should be denied" def test_expired_session_denied(self): """Expired session must be denied (simulated via missing session).""" result = auth_check_permission("expired_session_token", "cognitive_context") assert not result["allowed"], "Expired session should be denied" class TestProfileDeny: """Profile-based denials.""" def test_observer_denied_mutations(self): """OBSERVER cannot perform mutations.""" mutation_tools = [ "write_file", "cognitive_decide", "treasury_debit", "offsec_tem_transmute", ] for tool in mutation_tools: result = check_profile_permission(Profile.OBSERVER, tool) assert not result["allowed"], f"OBSERVER should be denied {tool}" def test_operator_denied_tem(self): """OPERATOR cannot invoke Tem.""" result = check_profile_permission(Profile.OPERATOR, "cognitive_invoke_tem") assert not result["allowed"], "OPERATOR should be denied Tem invocation" def test_guardian_denied_phoenix_ops(self): """GUARDIAN cannot perform Phoenix operations.""" phoenix_ops = [ "offsec_phoenix_enable", "offsec_phoenix_inject_crisis", ] for tool in phoenix_ops: result = check_profile_permission(Profile.GUARDIAN, tool) assert not result["allowed"], f"GUARDIAN should be denied {tool}" def test_phoenix_denied_treasury_create(self): """PHOENIX cannot create budgets (SOVEREIGN only).""" result = check_profile_permission(Profile.PHOENIX, "treasury_create_budget") assert not result["allowed"], "PHOENIX should be denied treasury creation" class TestSovereignRequiresHuman: """SOVEREIGN profile requires human verification.""" def test_sovereign_cannot_be_auto_granted(self): """ SOVEREIGN authority cannot be granted through normal dev session. This tests the constitutional invariant. """ # Dev session creates a session, but SOVEREIGN operations # should still require additional human verification session = auth_create_dev_session(scope="cognitive") token = session["token"] # Even with dev session, sovereign-only operations need proof # The dev session scope is "cognitive", not "vault" result = auth_check_permission(token, "treasury_create_budget") # This should be denied because cognitive scope doesn't include # treasury creation - that requires vault/sovereign scope # The key point: sovereign authority isn't auto-granted assert session["scope"] != "sovereign" or session.get("dev_mode"), ( "Production sessions should not auto-grant sovereign" ) class TestCollapseSemantics: """Authority collapse tests - always downward, never upward.""" def test_insufficient_profile_collapses(self): """When profile is insufficient, result indicates collapse target.""" result = check_profile_permission(Profile.OBSERVER, "cognitive_decide") assert not result["allowed"] # The denial should indicate the profile level assert result["profile"] == "observer" def test_profile_hierarchy_is_strict(self): """Profile hierarchy: OBSERVER < OPERATOR < GUARDIAN < PHOENIX < SOVEREIGN.""" profiles = [ Profile.OBSERVER, Profile.OPERATOR, Profile.GUARDIAN, Profile.PHOENIX, Profile.SOVEREIGN, ] # Each profile should have MORE tools than the one before prev_count = 0 for profile in profiles: from vaultmesh_mcp.tools.auth import PROFILE_TOOLS tool_count = len(PROFILE_TOOLS.get(profile, set())) assert tool_count >= prev_count, ( f"{profile.value} should have >= tools than previous profile" ) prev_count = tool_count