feat(reviewer-workflow): add hard wall against reviewer mutations through alternate profile or CLI side-channel
This commit is contained in:
@@ -37,6 +37,11 @@ from mcp_server import ( # noqa: E402
|
||||
from gitea_auth import get_profile # noqa: E402
|
||||
import gitea_config # noqa: E402
|
||||
|
||||
import mcp_server
|
||||
# Globally disable verification check for existing isolated unit tests
|
||||
_real_verify = mcp_server.verify_mutation_authority
|
||||
mcp_server.verify_mutation_authority = lambda *args, **kwargs: None
|
||||
|
||||
FAKE_AUTH = "Basic dGVzdDp0ZXN0"
|
||||
|
||||
|
||||
@@ -2296,3 +2301,90 @@ class TestIssueCommentPermissionSeparation(unittest.TestCase):
|
||||
"gitea.issue.comment", reviewer["allowed_operations"],
|
||||
reviewer.get("forbidden_operations", []))
|
||||
self.assertTrue(ok)
|
||||
|
||||
|
||||
class TestVerifyMutationAuthority(unittest.TestCase):
|
||||
"""Test verification lock logic under various configurations."""
|
||||
|
||||
def setUp(self):
|
||||
self.patch_profile = patch("mcp_server.get_profile")
|
||||
self.mock_profile = self.patch_profile.start()
|
||||
self.patch_username = patch("mcp_server._authenticated_username")
|
||||
self.mock_username = self.patch_username.start()
|
||||
|
||||
# Restore real function for these tests
|
||||
self._old_verify = mcp_server.verify_mutation_authority
|
||||
mcp_server.verify_mutation_authority = _real_verify
|
||||
|
||||
def tearDown(self):
|
||||
self.patch_profile.stop()
|
||||
self.patch_username.stop()
|
||||
mcp_server.verify_mutation_authority = self._old_verify
|
||||
# Clean up lock file
|
||||
if os.path.exists("/tmp/gitea_mutation_authority.lock"):
|
||||
os.remove("/tmp/gitea_mutation_authority.lock")
|
||||
|
||||
def test_missing_lock_fails_closed(self):
|
||||
if os.path.exists("/tmp/gitea_mutation_authority.lock"):
|
||||
os.remove("/tmp/gitea_mutation_authority.lock")
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
_real_verify("prgs")
|
||||
self.assertIn("lock is missing", str(ctx.exception))
|
||||
|
||||
def test_mismatched_remote_fails(self):
|
||||
with open("/tmp/gitea_mutation_authority.lock", "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"remote": "dadeschools",
|
||||
"current_profile": "prgs-reviewer",
|
||||
"current_identity": "sysadmin"
|
||||
}, f)
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
_real_verify("prgs")
|
||||
self.assertIn("does not match locked remote", str(ctx.exception))
|
||||
|
||||
def test_mismatched_profile_fails(self):
|
||||
with open("/tmp/gitea_mutation_authority.lock", "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"remote": "prgs",
|
||||
"current_profile": "prgs-author",
|
||||
"current_identity": "jcwalker3"
|
||||
}, f)
|
||||
self.mock_profile.return_value = {"profile_name": "prgs-reviewer"}
|
||||
self.mock_username.return_value = "sysadmin"
|
||||
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
_real_verify("prgs")
|
||||
self.assertIn("does not match locked authority", str(ctx.exception))
|
||||
|
||||
def test_author_to_reviewer_pivot_blocked_without_authorization(self):
|
||||
with open("/tmp/gitea_mutation_authority.lock", "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"remote": "prgs",
|
||||
"initial_profile": "prgs-author",
|
||||
"initial_identity": "jcwalker3",
|
||||
"current_profile": "prgs-reviewer",
|
||||
"current_identity": "sysadmin",
|
||||
"role_pivot_authorized": False
|
||||
}, f)
|
||||
self.mock_profile.return_value = {"profile_name": "prgs-reviewer"}
|
||||
self.mock_username.return_value = "sysadmin"
|
||||
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
_real_verify("prgs", required_role="reviewer")
|
||||
self.assertIn("without authorized role pivot", str(ctx.exception))
|
||||
|
||||
def test_allowed_when_match(self):
|
||||
with open("/tmp/gitea_mutation_authority.lock", "w", encoding="utf-8") as f:
|
||||
json.dump({
|
||||
"remote": "prgs",
|
||||
"initial_profile": "prgs-reviewer",
|
||||
"initial_identity": "sysadmin",
|
||||
"current_profile": "prgs-reviewer",
|
||||
"current_identity": "sysadmin",
|
||||
"role_pivot_authorized": False
|
||||
}, f)
|
||||
self.mock_profile.return_value = {"profile_name": "prgs-reviewer"}
|
||||
self.mock_username.return_value = "sysadmin"
|
||||
|
||||
# Should pass without exception
|
||||
_real_verify("prgs")
|
||||
|
||||
Reference in New Issue
Block a user