Require a sanctioned MCP daemon (or pytest) before get_auth_header and keychain credential fill so raw shell imports cannot bypass preflight gates.
85 lines
3.1 KiB
Python
85 lines
3.1 KiB
Python
"""Sanctioned MCP daemon guards for imports and credential access (#558).
|
|
|
|
Direct ``import gitea_mcp_server`` / ``import gitea_auth`` from a shell, plus
|
|
raw keychain dumps, bypass preflight purity and role gates. Mutation helpers
|
|
and keychain fallbacks therefore require an explicit sanctioned runtime.
|
|
|
|
Sanctioned contexts (any one):
|
|
- ``GITEA_MCP_SANCTIONED_DAEMON=1`` (set by the official MCP entrypoint)
|
|
- pytest (``PYTEST_CURRENT_TEST`` present)
|
|
- explicit operator opt-in ``GITEA_ALLOW_DIRECT_MCP_IMPORT=1`` (tests/tools only)
|
|
|
|
Credential keychain fill additionally allows:
|
|
- ``GITEA_ALLOW_KEYCHAIN_CLI=1`` for operator-only non-MCP scripts that must
|
|
use git-credential (never the default for LLM shells).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
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"
|
|
|
|
|
|
class UnsanctionedRuntimeError(RuntimeError):
|
|
"""Raised when mutation/credential code runs outside a sanctioned MCP daemon."""
|
|
|
|
|
|
def is_pytest_runtime() -> bool:
|
|
return bool((os.environ.get("PYTEST_CURRENT_TEST") or "").strip())
|
|
|
|
|
|
def is_sanctioned_mcp_daemon() -> bool:
|
|
if (os.environ.get(SANCTIONED_DAEMON_ENV) or "").strip() in {"1", "true", "yes"}:
|
|
return True
|
|
if (os.environ.get(ALLOW_DIRECT_IMPORT_ENV) or "").strip() in {"1", "true", "yes"}:
|
|
return True
|
|
if is_pytest_runtime():
|
|
return True
|
|
return False
|
|
|
|
|
|
def mark_sanctioned_daemon() -> None:
|
|
"""Call from the official MCP server entrypoint before serving tools."""
|
|
os.environ[SANCTIONED_DAEMON_ENV] = "1"
|
|
|
|
|
|
def assert_sanctioned_mutation_runtime(context: str = "mutation") -> None:
|
|
"""Fail closed when server mutation code is used outside the MCP daemon."""
|
|
if is_sanctioned_mcp_daemon():
|
|
return
|
|
raise UnsanctionedRuntimeError(
|
|
f"Unsanctioned runtime blocked {context} (#558). "
|
|
"Do not import gitea_mcp_server / call mutation helpers from a raw "
|
|
"shell or ad-hoc script. Use the official MCP daemon entrypoint "
|
|
f"(sets {SANCTIONED_DAEMON_ENV}=1), or run under pytest. "
|
|
f"Operator-only override: {ALLOW_DIRECT_IMPORT_ENV}=1 (not for LLM sessions)."
|
|
)
|
|
|
|
|
|
def assert_keychain_access_allowed() -> None:
|
|
"""Fail closed for git-credential keychain fill outside sanctioned contexts."""
|
|
if is_sanctioned_mcp_daemon():
|
|
return
|
|
if (os.environ.get(ALLOW_KEYCHAIN_CLI_ENV) or "").strip() in {"1", "true", "yes"}:
|
|
return
|
|
raise UnsanctionedRuntimeError(
|
|
"Unsanctioned keychain/credential fill blocked (#558). "
|
|
"Token extraction via git-credential is only allowed inside the "
|
|
f"official MCP daemon ({SANCTIONED_DAEMON_ENV}=1), pytest, or with "
|
|
f"explicit operator opt-in {ALLOW_KEYCHAIN_CLI_ENV}=1."
|
|
)
|
|
|
|
|
|
def runtime_status() -> dict[str, Any]:
|
|
return {
|
|
"sanctioned_daemon": is_sanctioned_mcp_daemon(),
|
|
"pytest": is_pytest_runtime(),
|
|
"sanctioned_env": SANCTIONED_DAEMON_ENV,
|
|
"allow_direct_import_env": ALLOW_DIRECT_IMPORT_ENV,
|
|
"allow_keychain_cli_env": ALLOW_KEYCHAIN_CLI_ENV,
|
|
}
|