init: vaultmesh mcp server
This commit is contained in:
140
tests/governance/test_auth_fail_closed.py
Normal file
140
tests/governance/test_auth_fail_closed.py
Normal file
@@ -0,0 +1,140 @@
|
||||
"""
|
||||
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
|
||||
Reference in New Issue
Block a user