fix(guard): pin session-state authority and reject direct-import mutation (#695)

Close remaining AC1/AC2/AC6-8 gaps after REQUEST_CHANGES and the PR #701
recurrence: GITEA_ALLOW_DIRECT_MCP_IMPORT never authorizes mutations;
production transport bind pins GITEA_MCP_SESSION_STATE_DIR so redirected
dirs cannot forge independent decision locks; mark/submit fail closed
offline; quarantine continues to void contaminated merge eligibility.

Regression tests reproduce the PR #701 direct-import + state-dir override
sequence. Full suite: 2665 passed, 6 skipped.

Closes #695 (remediation on PR #696)
This commit is contained in:
2026-07-13 21:48:28 -04:00
parent ae6f0b74db
commit 576349d545
6 changed files with 334 additions and 3 deletions
+11
View File
@@ -58,6 +58,17 @@ def _reset_mutation_authority(monkeypatch):
_fallback: str = state_dir,
_env_key: str = mcp_session_state.STATE_DIR_ENV,
) -> str:
# #695 AC2: when production native transport has pinned a session
# state root, that pin is authoritative even under test isolation
# (PR #701 redirected-state regression).
try:
import mcp_daemon_guard
pinned = mcp_daemon_guard.pinned_session_state_dir()
if pinned:
return pinned
except Exception:
pass
raw = (os.environ.get(_env_key) or "").strip()
return raw or _fallback
@@ -63,7 +63,16 @@ class TestNativeTransportBinding(unittest.TestCase):
mcp_daemon_guard.assert_sanctioned_mutation_runtime("offline_import")
msg = str(ctx.exception)
self.assertIn("#695", msg)
self.assertIn("not sufficient", msg.lower() + " " + msg)
# FORCE disables pytest allowance; direct-import env is rejected first
# (AC1). Without ALLOW_DIRECT, env-alone also yields "not sufficient".
lowered = msg.lower()
self.assertTrue(
"direct" in lowered
or "not sufficient" in lowered
or "allow_direct" in lowered
or "gitea_allow_direct" in lowered,
msg,
)
def test_direct_import_mark_rejected_outside_entrypoint(self):
mcp_daemon_guard.clear_native_runtime_for_tests()
@@ -677,5 +686,163 @@ class TestDocsStopAfterNativeFailure(unittest.TestCase):
self.assertIn("GITEA_ALLOW_DIRECT_MCP_IMPORT", doc)
class TestPR701DirectImportSessionOverrideSequence(unittest.TestCase):
"""AC9 regression: exact PR #701 contaminated-approval sequence must fail closed.
Observed attack:
- GITEA_ALLOW_DIRECT_MCP_IMPORT=1
- import mutation tools from gitea_mcp_server
- GITEA_MCP_SESSION_STATE_DIR=.mcp_session_701 (cross-PR lock evasion)
- mark_final + submit_pr_review
"""
def tearDown(self) -> None:
mcp_daemon_guard.clear_native_runtime_for_tests()
os.environ.pop(mcp_daemon_guard.FORCE_PROVENANCE_FAIL_ENV, None)
os.environ.pop(mcp_daemon_guard.SANCTIONED_DAEMON_ENV, None)
os.environ.pop(mcp_daemon_guard.ALLOW_DIRECT_IMPORT_ENV, None)
os.environ.pop(mcp_daemon_guard.SESSION_STATE_DIR_ENV, None)
def test_offline_run_submit_sequence_fails_closed(self):
"""Fresh interpreter: direct import + state-dir override cannot mark/submit."""
with tempfile.TemporaryDirectory() as tmp:
redirect = str(Path(tmp) / ".mcp_session_701")
snippet = textwrap.dedent(
f"""
import os
import sys
os.environ["GITEA_ALLOW_DIRECT_MCP_IMPORT"] = "1"
os.environ["GITEA_MCP_SESSION_STATE_DIR"] = {redirect!r}
os.environ["GITEA_MCP_PROFILE"] = "prgs-reviewer"
# No pytest modules in this subprocess.
import mcp_daemon_guard as g
assert g.is_native_mcp_transport() is False
assert g.is_production_native_mcp_transport() is False
try:
g.assert_sanctioned_mutation_runtime("run_submit_mark")
except g.UnsanctionedRuntimeError as exc:
msg = str(exc)
assert "GITEA_ALLOW_DIRECT_MCP_IMPORT" in msg or "#695" in msg
else:
raise SystemExit("direct-import env authorized mutation runtime")
try:
g.mark_sanctioned_daemon()
except g.UnsanctionedRuntimeError:
pass
else:
raise SystemExit("mark_sanctioned_daemon authorized offline import")
# Simulate decision-lock write into redirected dir only — must not
# establish native authority.
import mcp_session_state as ss
wrote = ss.save_state(
kind=ss.KIND_DECISION_LOCK,
payload={{
"final_review_decision_ready": True,
"ready_pr_number": 701,
"ready_action": "approve",
"ready_expected_head_sha": "6b675f5c834b41f9d74e8a54294ff44dddf28ae4",
"session_profile": "prgs-reviewer",
"session_profile_lock": "prgs-reviewer",
"remote": "prgs",
}},
profile_identity="prgs-reviewer",
state_dir={redirect!r},
)
assert wrote is not None
assert g.is_native_mcp_transport() is False
try:
g.assert_no_direct_import_bypass("gitea_submit_pr_review")
except g.UnsanctionedRuntimeError:
pass
else:
raise SystemExit("direct-import bypass accepted for submit")
print("OK")
"""
)
proc = _run_offline_snippet(snippet)
self.assertEqual(proc.returncode, 0, proc.stdout + proc.stderr)
self.assertIn("OK", proc.stdout)
def test_session_state_dir_pin_ignores_post_bind_redirect(self):
"""AC2: after production bind, env STATE_DIR override is ignored."""
mcp_daemon_guard.clear_native_runtime_for_tests()
import mcp_session_state
with tempfile.TemporaryDirectory() as tmp:
legitimate = str(Path(tmp) / "legitimate-state")
rogue = str(Path(tmp) / ".mcp_session_701")
os.makedirs(legitimate, mode=0o700, exist_ok=True)
os.makedirs(rogue, mode=0o700, exist_ok=True)
os.environ[mcp_daemon_guard.SESSION_STATE_DIR_ENV] = legitimate
canonical = str((REPO_ROOT / "mcp_server.py").resolve())
def _fake_caller():
return canonical
with patch.object(
mcp_daemon_guard,
"_caller_official_entrypoint_path",
side_effect=_fake_caller,
):
with patch.object(
mcp_daemon_guard, "is_pytest_runtime", return_value=False
):
mcp_daemon_guard.mark_sanctioned_daemon()
mcp_daemon_guard.bind_native_mcp_transport(transport="stdio")
pinned = mcp_daemon_guard.pinned_session_state_dir()
self.assertEqual(pinned, str(Path(legitimate).resolve()))
# Attacker redirects env after bind (PR #701).
os.environ[mcp_daemon_guard.SESSION_STATE_DIR_ENV] = rogue
self.assertEqual(
mcp_daemon_guard.pinned_session_state_dir(),
str(Path(legitimate).resolve()),
)
self.assertEqual(
mcp_session_state.default_state_dir(),
str(Path(legitimate).resolve()),
)
self.assertNotEqual(
mcp_session_state.default_state_dir(),
str(Path(rogue).resolve()),
)
# Unpinned env view still sees rogue (diagnostics only).
unpinned = mcp_session_state.env_session_state_dir_unpinned()
self.assertTrue(
unpinned == rogue
or Path(unpinned).resolve() == Path(rogue).resolve(),
unpinned,
)
def test_direct_import_env_does_not_authorize_under_force_unsanctioned(self):
mcp_daemon_guard.clear_native_runtime_for_tests()
os.environ[mcp_daemon_guard.ALLOW_DIRECT_IMPORT_ENV] = "1"
os.environ[mcp_daemon_guard.FORCE_PROVENANCE_FAIL_ENV] = "1"
self.assertTrue(mcp_daemon_guard.direct_import_env_enabled())
# Under pytest, assert_no_direct_import_bypass is a no-op; FORCE path
# still blocks is_native / assert_sanctioned via force-unsanctioned.
self.assertFalse(mcp_daemon_guard.is_native_mcp_transport())
with self.assertRaises(mcp_daemon_guard.UnsanctionedRuntimeError):
mcp_daemon_guard.assert_sanctioned_mutation_runtime("direct-import")
def test_quarantine_voids_merge_approval_for_contaminated_review(self):
"""AC6AC8: quarantined APPROVED does not satisfy merge approval head."""
entry = {
"verdict": "APPROVED",
"dismissed": False,
"reviewed_head_sha": "6b675f5c834b41f9d74e8a54294ff44dddf28ae4",
"review_id": 431,
"submitted_at": "2026-07-13T23:52:34Z",
"quarantined": True,
}
result = merge_approval_gate.assess_merge_approval_head(
current_head_sha="6b675f5c834b41f9d74e8a54294ff44dddf28ae4",
latest_by_reviewer={"sysadmin": entry},
quarantined_review_ids={431},
)
self.assertFalse(result["approval_at_current_head"])
self.assertEqual(result["quarantined_approvals_at_current_head"], 1)
self.assertIn("quarantined", (result["stale_approval_block_reason"] or ""))
if __name__ == "__main__":
unittest.main()