Files
Gitea-Tools/mcp_daemon_guard.py
T
sysadmin 253269c61b feat(guard): native MCP transport binding and contaminated-review quarantine (Closes #695)
Bind mutation/credential paths to a process-local native MCP runtime so env
spoofing, direct imports, and offline helpers cannot reconstruct session gates
after native transport failure. Quarantine contaminated formal reviews under
controller authority and honor quarantine in review feedback, merge eligibility,
merge mutation, and canonical handoff validation. Add regression coverage for
the second (PR #694 / review 427) incident class.
2026-07-13 04:17:57 -04:00

213 lines
7.8 KiB
Python

"""Sanctioned MCP daemon guards for imports and credential access (#558 / #695).
Direct ``import gitea_mcp_server`` from a shell bypasses native MCP transport.
#558 introduced a daemon marker; #695 hardens it so:
- Environment variables alone cannot reconstruct a native session
(``GITEA_MCP_SANCTIONED_DAEMON=1`` / ``GITEA_ALLOW_DIRECT_MCP_IMPORT=1`` are
insufficient for mutation gates).
- A process-local runtime record is created only by the official entrypoint
(``mcp_server.py`` calling ``mark_sanctioned_daemon``), bound to PID and a
random secret that never leaves process memory.
- Offline scripts that import internals fail closed on mutations.
- Pytest remains allowed (hermetic tests); optional force flags exist for
provenance regression tests.
Manual deletion of session-state files is never a recovery path.
"""
from __future__ import annotations
import hashlib
import inspect
import os
import secrets
import time
from typing import Any
SANCTIONED_DAEMON_ENV = "GITEA_MCP_SANCTIONED_DAEMON"
ALLOW_DIRECT_IMPORT_ENV = "GITEA_ALLOW_DIRECT_MCP_IMPORT"
ALLOW_KEYCHAIN_CLI_ENV = "GITEA_ALLOW_KEYCHAIN_CLI"
# Test-only: force provenance failure even under pytest (#695 regressions).
FORCE_PROVENANCE_FAIL_ENV = "GITEA_TEST_FORCE_UNSANCTIONED"
# Process-local native runtime (never persisted, never read from env alone).
_NATIVE_RUNTIME: dict[str, Any] | None = None
class UnsanctionedRuntimeError(RuntimeError):
"""Raised when mutation/credential code runs outside a native MCP daemon."""
def is_pytest_runtime() -> bool:
if (os.environ.get(FORCE_PROVENANCE_FAIL_ENV) or "").strip() in {
"1",
"true",
"yes",
}:
return False
import sys
if "pytest" in sys.modules:
return True
return bool((os.environ.get("PYTEST_CURRENT_TEST") or "").strip())
def _caller_is_official_entrypoint() -> bool:
"""True when mark_sanctioned_daemon is invoked from mcp_server.py."""
for frame in inspect.stack()[1:12]:
path = (frame.filename or "").replace("\\", "/")
base = path.rsplit("/", 1)[-1]
if base == "mcp_server.py":
return True
return False
def mark_sanctioned_daemon(*, allow_test_bootstrap: bool = False) -> dict[str, Any]:
"""Mark this process as the official native MCP daemon (#695).
Only the official ``mcp_server.py`` entrypoint (or pytest test bootstrap)
may establish native transport. Setting env vars alone is insufficient.
"""
global _NATIVE_RUNTIME
if not is_pytest_runtime() and not allow_test_bootstrap:
if not _caller_is_official_entrypoint():
raise UnsanctionedRuntimeError(
"mark_sanctioned_daemon rejected: not called from official "
"mcp_server.py entrypoint (#695). Offline import / standalone "
"scripts cannot reconstruct native transport. Stop after native "
"MCP failure; do not run offline mutation helpers."
)
token = secrets.token_hex(32)
_NATIVE_RUNTIME = {
"token": token,
"token_fingerprint": hashlib.sha256(token.encode()).hexdigest()[:16],
"pid": os.getpid(),
"started_at": time.time(),
"entrypoint": "mcp_server",
}
# Legacy signal for older probes; alone does not authorize mutations.
os.environ[SANCTIONED_DAEMON_ENV] = "1"
return native_runtime_status()
def clear_native_runtime_for_tests() -> None:
"""Test helper: drop native runtime (does not clear env)."""
global _NATIVE_RUNTIME
_NATIVE_RUNTIME = None
def is_native_mcp_transport() -> bool:
"""True when this process holds a live native MCP runtime record (#695)."""
if (os.environ.get(FORCE_PROVENANCE_FAIL_ENV) or "").strip() in {
"1",
"true",
"yes",
}:
return False
if _NATIVE_RUNTIME is None:
return False
if int(_NATIVE_RUNTIME.get("pid") or -1) != os.getpid():
return False
if not (_NATIVE_RUNTIME.get("token") or "").strip():
return False
return True
def is_sanctioned_mcp_daemon() -> bool:
"""Backward-compatible name; #695 requires native transport, not env alone."""
if is_native_mcp_transport():
return True
if is_pytest_runtime():
return True
# Explicit direct-import override is for non-LLM operator/test tools only.
# It is deliberately ignored when a native runtime is expected for mutations
# under LLM sessions (tests use pytest path). Env alone never grants native.
return False
def assert_sanctioned_mutation_runtime(context: str = "mutation") -> None:
"""Fail closed when mutation code runs outside native MCP transport (#695)."""
if is_sanctioned_mcp_daemon():
return
env_spoof = (os.environ.get(SANCTIONED_DAEMON_ENV) or "").strip() in {
"1",
"true",
"yes",
}
extra = ""
if env_spoof:
extra = (
f" Note: {SANCTIONED_DAEMON_ENV} alone is not sufficient (#695); "
"native transport requires the official MCP entrypoint."
)
raise UnsanctionedRuntimeError(
f"Unsanctioned / non-native runtime blocked {context} (#695). "
"Do not import gitea_mcp_server or call mutation helpers from a raw "
"shell, offline runner, or ad-hoc script after native MCP failure. "
"Stop and reconnect the official MCP daemon (mcp_server.py). "
f"Do not set {ALLOW_DIRECT_IMPORT_ENV} or raw token env vars in LLM "
f"sessions.{extra}"
)
def assert_keychain_access_allowed() -> None:
"""Fail closed for git-credential keychain fill outside sanctioned contexts."""
if is_sanctioned_mcp_daemon():
return
# Operator-only keychain CLI remains available outside LLM mutation path.
if (os.environ.get(ALLOW_KEYCHAIN_CLI_ENV) or "").strip() in {"1", "true", "yes"}:
if not is_pytest_runtime():
# Still block pure env spoof of SANCTIONED_DAEMON for keychain when
# FORCE is set for tests.
if (os.environ.get(FORCE_PROVENANCE_FAIL_ENV) or "").strip() in {
"1",
"true",
"yes",
}:
pass
else:
return
raise UnsanctionedRuntimeError(
"Unsanctioned keychain/credential fill blocked (#558/#695). "
"Token extraction via git-credential is only allowed inside the "
f"official native MCP daemon or with explicit operator opt-in "
f"{ALLOW_KEYCHAIN_CLI_ENV}=1 (never for offline mutation runners)."
)
def native_runtime_status() -> dict[str, Any]:
"""LLM-safe native runtime status (no raw token)."""
rt = _NATIVE_RUNTIME or {}
return {
"native_mcp_transport": is_native_mcp_transport(),
"pytest": is_pytest_runtime(),
"pid": rt.get("pid"),
"token_fingerprint": rt.get("token_fingerprint"),
"started_at": rt.get("started_at"),
"entrypoint": rt.get("entrypoint"),
"env_sanctioned_alone_insufficient": True,
"sanctioned_env": SANCTIONED_DAEMON_ENV,
"allow_direct_import_env": ALLOW_DIRECT_IMPORT_ENV,
"allow_keychain_cli_env": ALLOW_KEYCHAIN_CLI_ENV,
}
def runtime_status() -> dict[str, Any]:
"""Backward-compatible status payload."""
status = native_runtime_status()
status["sanctioned_daemon"] = is_sanctioned_mcp_daemon()
return status
def mutation_provenance_fields() -> dict[str, Any]:
"""Fields to attach to live mutation / review audit records (#695 AC6)."""
st = native_runtime_status()
return {
"transport": "native_mcp" if st["native_mcp_transport"] else "untrusted",
"native_mcp_transport": bool(st["native_mcp_transport"]),
"native_runtime_pid": st.get("pid"),
"native_token_fingerprint": st.get("token_fingerprint"),
"entrypoint": st.get("entrypoint"),
}