feat(mcp): enforce strict cross-project mutation boundaries (Closes #707)
This commit is contained in:
@@ -0,0 +1,94 @@
|
||||
import os
|
||||
import sys
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import remote_repo_guard
|
||||
import gitea_mcp_server as server
|
||||
from session_context_binding import _reset_session_context_for_testing, bind_session_context
|
||||
|
||||
|
||||
LOCAL_GITEA_TOOLS_URL = "https://gitea.prgs.cc/Scaled-Tech-Consulting/Gitea-Tools.git"
|
||||
|
||||
|
||||
class TestCrossProjectMutationBoundary(unittest.TestCase):
|
||||
def setUp(self):
|
||||
os.environ["PYTEST_CURRENT_TEST"] = "test"
|
||||
_reset_session_context_for_testing()
|
||||
|
||||
def tearDown(self):
|
||||
_reset_session_context_for_testing()
|
||||
os.environ.pop("PYTEST_CURRENT_TEST", None)
|
||||
|
||||
def test_cross_project_codebase_mutation_blocked(self):
|
||||
"""Codebase mutations targeting another project must fail closed (#707)."""
|
||||
assessment = remote_repo_guard.assess_remote_repo_match(
|
||||
remote="prgs",
|
||||
resolved_org="Other-Org",
|
||||
resolved_repo="Other-Repo",
|
||||
local_remote_url=LOCAL_GITEA_TOOLS_URL,
|
||||
org_explicit=True,
|
||||
repo_explicit=True,
|
||||
for_mutation=True,
|
||||
)
|
||||
self.assertTrue(assessment["block"])
|
||||
self.assertTrue(assessment.get("cross_project_mutation_block"))
|
||||
self.assertEqual(assessment["primary_org"], "Scaled-Tech-Consulting")
|
||||
self.assertEqual(assessment["primary_repo"], "Gitea-Tools")
|
||||
|
||||
err_msg = remote_repo_guard.format_remote_repo_guard_error(assessment)
|
||||
self.assertIn("Cross-project mutation guard (#707)", err_msg)
|
||||
self.assertIn("Attempted codebase mutation targeting 'Other-Org/Other-Repo'", err_msg)
|
||||
self.assertIn("outside of primary authorized project context 'Scaled-Tech-Consulting/Gitea-Tools'", err_msg)
|
||||
self.assertIn("create an issue in the target repository ('Other-Org/Other-Repo') instead", err_msg)
|
||||
|
||||
def test_same_project_codebase_mutation_allowed(self):
|
||||
"""Codebase mutations targeting the primary project context are allowed."""
|
||||
assessment = remote_repo_guard.assess_remote_repo_match(
|
||||
remote="prgs",
|
||||
resolved_org="Scaled-Tech-Consulting",
|
||||
resolved_repo="Gitea-Tools",
|
||||
local_remote_url=LOCAL_GITEA_TOOLS_URL,
|
||||
org_explicit=True,
|
||||
repo_explicit=True,
|
||||
for_mutation=True,
|
||||
)
|
||||
self.assertFalse(assessment["block"])
|
||||
self.assertFalse(assessment.get("cross_project_mutation_block", False))
|
||||
|
||||
def test_cross_project_metadata_operation_allowed(self):
|
||||
"""Metadata operations (issue creation, comments) across project boundaries are allowed."""
|
||||
assessment = remote_repo_guard.assess_remote_repo_match(
|
||||
remote="prgs",
|
||||
resolved_org="Other-Org",
|
||||
resolved_repo="Other-Repo",
|
||||
local_remote_url=LOCAL_GITEA_TOOLS_URL,
|
||||
org_explicit=True,
|
||||
repo_explicit=True,
|
||||
for_mutation=False,
|
||||
)
|
||||
self.assertTrue(assessment["proven"])
|
||||
self.assertFalse(assessment["block"])
|
||||
|
||||
@mock.patch.dict(os.environ, {"GITEA_FORCE_REMOTE_REPO_CHECK": "1"})
|
||||
@mock.patch("gitea_mcp_server._local_git_remote_url", return_value=LOCAL_GITEA_TOOLS_URL)
|
||||
def test_mcp_server_resolve_cross_project_mutation_fails_closed(self, mock_remote):
|
||||
"""_resolve with for_mutation=True blocks cross-project targets."""
|
||||
with self.assertRaises(RuntimeError) as ctx:
|
||||
server._resolve("prgs", None, "Other-Org", "Other-Repo", for_mutation=True)
|
||||
|
||||
err = str(ctx.exception)
|
||||
self.assertIn("Cross-project mutation guard (#707)", err)
|
||||
self.assertIn("create an issue in the target repository", err)
|
||||
|
||||
@mock.patch.dict(os.environ, {"GITEA_FORCE_REMOTE_REPO_CHECK": "1"})
|
||||
@mock.patch("gitea_mcp_server._local_git_remote_url", return_value=LOCAL_GITEA_TOOLS_URL)
|
||||
def test_mcp_server_resolve_cross_project_metadata_succeeds(self, mock_remote):
|
||||
"""_resolve with for_mutation=False allows explicit cross-project target."""
|
||||
host, org, repo = server._resolve("prgs", None, "Other-Org", "Other-Repo", for_mutation=False)
|
||||
self.assertEqual(org, "Other-Org")
|
||||
self.assertEqual(repo, "Other-Repo")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user