fix(guard): close allow_test_bootstrap and basename entrypoint bypasses (#695)
Remove the public allow_test_bootstrap production seam so caller-controlled flags cannot forge native mutation provenance. Bind provenance to the resolved canonical entrypoint path plus a live stdio transport bind; basename-only mcp_server.py stack frames and import-only launch no longer authorize mutations. Test-mode install_test_native_runtime is pytest-only and cannot reach production Gitea mutation endpoints. Add AC9 regressions for both reviewer-found bypasses and related spoof vectors. Refs: PR #696 REQUEST_CHANGES at 253269c; issue #695 comments 11002/11005.
This commit is contained in:
+268
-34
@@ -6,12 +6,18 @@ Direct ``import gitea_mcp_server`` from a shell bypasses native MCP transport.
|
||||
- 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.
|
||||
- A process-local runtime record is established only by the resolved canonical
|
||||
entrypoint path (not basename) **and** the actual native MCP transport
|
||||
lifecycle (``bind_native_mcp_transport`` before ``mcp.run``). Merely
|
||||
importing or launching the entrypoint offline does not grant mutation
|
||||
authority.
|
||||
- Public caller-controlled flags (including any former
|
||||
``allow_test_bootstrap``) never establish trusted mutation provenance.
|
||||
- Offline scripts that import internals fail closed on mutations.
|
||||
- Pytest remains allowed (hermetic tests); optional force flags exist for
|
||||
provenance regression tests.
|
||||
- Pytest remains allowed for hermetic unit tests via ``is_pytest_runtime()``.
|
||||
A separate test-only seam may establish a **test-mode** native record for
|
||||
unit tests of transport gates; that record cannot authorize production
|
||||
Gitea mutation endpoints.
|
||||
|
||||
Manual deletion of session-state files is never a recovery path.
|
||||
"""
|
||||
@@ -23,6 +29,7 @@ import inspect
|
||||
import os
|
||||
import secrets
|
||||
import time
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
|
||||
SANCTIONED_DAEMON_ENV = "GITEA_MCP_SANCTIONED_DAEMON"
|
||||
@@ -34,6 +41,13 @@ 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
|
||||
|
||||
# Production transport identifiers accepted by bind_native_mcp_transport.
|
||||
_PRODUCTION_TRANSPORTS = frozenset({"stdio"})
|
||||
_RUNTIME_MODE_PRODUCTION = "production"
|
||||
_RUNTIME_MODE_TEST = "test"
|
||||
_PHASE_ENTRYPOINT_CLAIMED = "entrypoint_claimed"
|
||||
_PHASE_TRANSPORT_BOUND = "transport_bound"
|
||||
|
||||
|
||||
class UnsanctionedRuntimeError(RuntimeError):
|
||||
"""Raised when mutation/credential code runs outside a native MCP daemon."""
|
||||
@@ -53,44 +67,197 @@ def is_pytest_runtime() -> bool:
|
||||
return bool((os.environ.get("PYTEST_CURRENT_TEST") or "").strip())
|
||||
|
||||
|
||||
def _package_root() -> Path:
|
||||
"""Directory that contains the canonical MCP entrypoint modules."""
|
||||
return Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def canonical_entrypoint_paths() -> frozenset[str]:
|
||||
"""Resolved absolute paths of official entrypoints (not basenames)."""
|
||||
root = _package_root()
|
||||
return frozenset(
|
||||
{
|
||||
str((root / "mcp_server.py").resolve()),
|
||||
str((root / "gitea_mcp_server.py").resolve()),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
def _resolve_path(path: str | None) -> str | None:
|
||||
if not path:
|
||||
return None
|
||||
try:
|
||||
return str(Path(path).resolve())
|
||||
except (OSError, RuntimeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def _caller_official_entrypoint_path() -> str | None:
|
||||
"""Return the resolved canonical entrypoint path in the call stack, or None.
|
||||
|
||||
Basename-only matches (e.g. an attacker file named ``mcp_server.py``
|
||||
elsewhere) are rejected. The path must equal one of
|
||||
:func:`canonical_entrypoint_paths`.
|
||||
"""
|
||||
canonical = canonical_entrypoint_paths()
|
||||
for frame in inspect.stack()[1:20]:
|
||||
resolved = _resolve_path(frame.filename)
|
||||
if resolved and resolved in canonical:
|
||||
return resolved
|
||||
return None
|
||||
|
||||
|
||||
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
|
||||
"""True when invoked from a resolved canonical entrypoint path (#695)."""
|
||||
return _caller_official_entrypoint_path() is not None
|
||||
|
||||
|
||||
def mark_sanctioned_daemon(*, allow_test_bootstrap: bool = False) -> dict[str, Any]:
|
||||
"""Mark this process as the official native MCP daemon (#695).
|
||||
def _new_runtime_token() -> tuple[str, str]:
|
||||
token = secrets.token_hex(32)
|
||||
fingerprint = hashlib.sha256(token.encode()).hexdigest()[:16]
|
||||
return token, fingerprint
|
||||
|
||||
Only the official ``mcp_server.py`` entrypoint (or pytest test bootstrap)
|
||||
may establish native transport. Setting env vars alone is insufficient.
|
||||
|
||||
def mark_sanctioned_daemon() -> dict[str, Any]:
|
||||
"""Claim the official entrypoint for this process (#695).
|
||||
|
||||
This alone does **not** authorize mutations. Callers must subsequently
|
||||
bind the native MCP transport via :func:`bind_native_mcp_transport`.
|
||||
|
||||
Only a stack frame whose **resolved absolute path** is the canonical
|
||||
``mcp_server.py`` or ``gitea_mcp_server.py`` next to this module may
|
||||
claim the entrypoint. Basename spoofing is rejected.
|
||||
|
||||
There is no public ``allow_test_bootstrap`` argument: caller-controlled
|
||||
flags must never establish trusted mutation provenance. Hermetic tests
|
||||
use :func:`install_test_native_runtime` (pytest-only, test mode).
|
||||
"""
|
||||
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)
|
||||
if is_pytest_runtime():
|
||||
# Under pytest, production mark is a no-op for transport authority.
|
||||
# Tests that need a native-transport record use install_test_native_runtime.
|
||||
return native_runtime_status()
|
||||
|
||||
entrypoint_path = _caller_official_entrypoint_path()
|
||||
if entrypoint_path is None:
|
||||
raise UnsanctionedRuntimeError(
|
||||
"mark_sanctioned_daemon rejected: not called from the resolved "
|
||||
"canonical MCP entrypoint path (#695). Basename-only names "
|
||||
"(e.g. a renamed runner called mcp_server.py) are insufficient. "
|
||||
"Offline import / standalone scripts cannot reconstruct native "
|
||||
"transport. Stop after native MCP failure; do not run offline "
|
||||
"mutation helpers."
|
||||
)
|
||||
|
||||
token, fingerprint = _new_runtime_token()
|
||||
_NATIVE_RUNTIME = {
|
||||
"token": token,
|
||||
"token_fingerprint": hashlib.sha256(token.encode()).hexdigest()[:16],
|
||||
"token_fingerprint": fingerprint,
|
||||
"pid": os.getpid(),
|
||||
"started_at": time.time(),
|
||||
"entrypoint": "mcp_server",
|
||||
"entrypoint_path": entrypoint_path,
|
||||
"phase": _PHASE_ENTRYPOINT_CLAIMED,
|
||||
"transport": None,
|
||||
"mode": _RUNTIME_MODE_PRODUCTION,
|
||||
}
|
||||
# Legacy signal for older probes; alone does not authorize mutations.
|
||||
os.environ[SANCTIONED_DAEMON_ENV] = "1"
|
||||
return native_runtime_status()
|
||||
|
||||
|
||||
def bind_native_mcp_transport(*, transport: str) -> dict[str, Any]:
|
||||
"""Bind the live native MCP transport lifecycle (#695).
|
||||
|
||||
Must be called from the resolved canonical entrypoint immediately before
|
||||
the real MCP server transport loop (e.g. ``mcp.run(transport=\"stdio\")``).
|
||||
Requires a prior successful :func:`mark_sanctioned_daemon` claim in this
|
||||
process. Import-only or offline launch without this bind leaves
|
||||
:func:`is_native_mcp_transport` false.
|
||||
"""
|
||||
global _NATIVE_RUNTIME
|
||||
transport_name = (transport or "").strip().lower()
|
||||
if transport_name not in _PRODUCTION_TRANSPORTS:
|
||||
raise UnsanctionedRuntimeError(
|
||||
f"bind_native_mcp_transport rejected: transport {transport!r} is "
|
||||
f"not a production MCP transport (#695). Allowed: "
|
||||
f"{sorted(_PRODUCTION_TRANSPORTS)}."
|
||||
)
|
||||
|
||||
entrypoint_path = _caller_official_entrypoint_path()
|
||||
if entrypoint_path is None:
|
||||
raise UnsanctionedRuntimeError(
|
||||
"bind_native_mcp_transport rejected: not called from the resolved "
|
||||
"canonical MCP entrypoint path (#695)."
|
||||
)
|
||||
|
||||
if _NATIVE_RUNTIME is None or int(_NATIVE_RUNTIME.get("pid") or -1) != os.getpid():
|
||||
raise UnsanctionedRuntimeError(
|
||||
"bind_native_mcp_transport rejected: no entrypoint claim in this "
|
||||
"process (#695). Call mark_sanctioned_daemon() from the official "
|
||||
"entrypoint first."
|
||||
)
|
||||
|
||||
if _NATIVE_RUNTIME.get("mode") != _RUNTIME_MODE_PRODUCTION:
|
||||
raise UnsanctionedRuntimeError(
|
||||
"bind_native_mcp_transport rejected: runtime mode is not "
|
||||
"production (#695)."
|
||||
)
|
||||
|
||||
claimed = (_NATIVE_RUNTIME.get("entrypoint_path") or "").strip()
|
||||
if claimed and claimed != entrypoint_path:
|
||||
raise UnsanctionedRuntimeError(
|
||||
"bind_native_mcp_transport rejected: entrypoint path mismatch "
|
||||
"between mark and bind (#695)."
|
||||
)
|
||||
|
||||
_NATIVE_RUNTIME["phase"] = _PHASE_TRANSPORT_BOUND
|
||||
_NATIVE_RUNTIME["transport"] = transport_name
|
||||
_NATIVE_RUNTIME["entrypoint_path"] = entrypoint_path
|
||||
_NATIVE_RUNTIME["bound_at"] = time.time()
|
||||
os.environ[SANCTIONED_DAEMON_ENV] = "1"
|
||||
return native_runtime_status()
|
||||
|
||||
|
||||
def install_test_native_runtime() -> dict[str, Any]:
|
||||
"""Pytest-only seam for hermetic native-transport unit tests (#695).
|
||||
|
||||
Establishes a **test-mode** process-local record so unit tests can exercise
|
||||
gates that require ``is_native_mcp_transport()``. This record:
|
||||
|
||||
- is rejected outside pytest (including a fresh offline interpreter);
|
||||
- never uses production mode;
|
||||
- cannot authorize production Gitea mutation endpoints
|
||||
(:func:`assert_production_mutation_runtime` / production path of
|
||||
:func:`assert_sanctioned_mutation_runtime` when not under pytest).
|
||||
|
||||
There is no public caller-controlled flag that forges production native
|
||||
transport.
|
||||
"""
|
||||
global _NATIVE_RUNTIME
|
||||
if not is_pytest_runtime():
|
||||
raise UnsanctionedRuntimeError(
|
||||
"install_test_native_runtime rejected: test-mode native runtime "
|
||||
"is only available under pytest (#695). allow_test_bootstrap and "
|
||||
"similar caller-controlled flags do not exist and cannot authorize "
|
||||
"a fresh offline interpreter."
|
||||
)
|
||||
token, fingerprint = _new_runtime_token()
|
||||
_NATIVE_RUNTIME = {
|
||||
"token": token,
|
||||
"token_fingerprint": fingerprint,
|
||||
"pid": os.getpid(),
|
||||
"started_at": time.time(),
|
||||
"entrypoint": "test_bootstrap",
|
||||
"entrypoint_path": None,
|
||||
"phase": _PHASE_TRANSPORT_BOUND,
|
||||
"transport": "test",
|
||||
"mode": _RUNTIME_MODE_TEST,
|
||||
"bound_at": time.time(),
|
||||
}
|
||||
return native_runtime_status()
|
||||
|
||||
|
||||
def clear_native_runtime_for_tests() -> None:
|
||||
"""Test helper: drop native runtime (does not clear env)."""
|
||||
global _NATIVE_RUNTIME
|
||||
@@ -98,7 +265,7 @@ def clear_native_runtime_for_tests() -> None:
|
||||
|
||||
|
||||
def is_native_mcp_transport() -> bool:
|
||||
"""True when this process holds a live native MCP runtime record (#695)."""
|
||||
"""True when this process holds a transport-bound native runtime (#695)."""
|
||||
if (os.environ.get(FORCE_PROVENANCE_FAIL_ENV) or "").strip() in {
|
||||
"1",
|
||||
"true",
|
||||
@@ -111,12 +278,23 @@ def is_native_mcp_transport() -> bool:
|
||||
return False
|
||||
if not (_NATIVE_RUNTIME.get("token") or "").strip():
|
||||
return False
|
||||
if _NATIVE_RUNTIME.get("phase") != _PHASE_TRANSPORT_BOUND:
|
||||
return False
|
||||
if not (_NATIVE_RUNTIME.get("transport") or "").strip():
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def is_production_native_mcp_transport() -> bool:
|
||||
"""True only for production-mode, transport-bound native runtime."""
|
||||
if not is_native_mcp_transport():
|
||||
return False
|
||||
return (_NATIVE_RUNTIME or {}).get("mode") == _RUNTIME_MODE_PRODUCTION
|
||||
|
||||
|
||||
def is_sanctioned_mcp_daemon() -> bool:
|
||||
"""Backward-compatible name; #695 requires native transport, not env alone."""
|
||||
if is_native_mcp_transport():
|
||||
if is_production_native_mcp_transport():
|
||||
return True
|
||||
if is_pytest_runtime():
|
||||
return True
|
||||
@@ -126,10 +304,42 @@ def is_sanctioned_mcp_daemon() -> bool:
|
||||
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():
|
||||
def assert_production_mutation_runtime(context: str = "mutation") -> None:
|
||||
"""Fail closed unless production native MCP transport is bound (#695).
|
||||
|
||||
Test-mode bootstrap records and pytest-only hermetic allowances do **not**
|
||||
satisfy this gate. Use for production Gitea mutation endpoints that must
|
||||
never be reachable via test bootstrap.
|
||||
"""
|
||||
if is_production_native_mcp_transport():
|
||||
return
|
||||
mode = (_NATIVE_RUNTIME or {}).get("mode")
|
||||
if mode == _RUNTIME_MODE_TEST:
|
||||
raise UnsanctionedRuntimeError(
|
||||
f"Test-mode native runtime cannot authorize production {context} "
|
||||
"(#695). install_test_native_runtime / former allow_test_bootstrap "
|
||||
"must never reach real Gitea mutation endpoints."
|
||||
)
|
||||
assert_sanctioned_mutation_runtime(context)
|
||||
|
||||
|
||||
def assert_sanctioned_mutation_runtime(context: str = "mutation") -> None:
|
||||
"""Fail closed when mutation code runs outside native MCP transport (#695).
|
||||
|
||||
Under pytest, hermetic unit tests are allowed (profile/permission tests).
|
||||
Outside pytest, requires production-mode transport-bound native runtime.
|
||||
Test-mode records do not authorize non-pytest production mutations.
|
||||
"""
|
||||
if is_pytest_runtime():
|
||||
return
|
||||
if is_production_native_mcp_transport():
|
||||
return
|
||||
mode = (_NATIVE_RUNTIME or {}).get("mode")
|
||||
if mode == _RUNTIME_MODE_TEST:
|
||||
raise UnsanctionedRuntimeError(
|
||||
f"Test-mode native runtime cannot authorize production {context} "
|
||||
"(#695). Test bootstrap cannot reach production mutation endpoints."
|
||||
)
|
||||
env_spoof = (os.environ.get(SANCTIONED_DAEMON_ENV) or "").strip() in {
|
||||
"1",
|
||||
"true",
|
||||
@@ -139,13 +349,24 @@ def assert_sanctioned_mutation_runtime(context: str = "mutation") -> None:
|
||||
if env_spoof:
|
||||
extra = (
|
||||
f" Note: {SANCTIONED_DAEMON_ENV} alone is not sufficient (#695); "
|
||||
"native transport requires the official MCP entrypoint."
|
||||
"native transport requires the official MCP entrypoint and a live "
|
||||
"transport bind."
|
||||
)
|
||||
phase = (_NATIVE_RUNTIME or {}).get("phase")
|
||||
if phase == _PHASE_ENTRYPOINT_CLAIMED:
|
||||
extra = (
|
||||
(extra + " ") if extra else " "
|
||||
) + (
|
||||
"Entrypoint was claimed but native MCP transport was never bound "
|
||||
"(#695); offline launch/import of the real entrypoint does not "
|
||||
"grant mutation authority."
|
||||
)
|
||||
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). "
|
||||
"Stop and reconnect the official MCP daemon (mcp_server.py) over "
|
||||
"native transport. "
|
||||
f"Do not set {ALLOW_DIRECT_IMPORT_ENV} or raw token env vars in LLM "
|
||||
f"sessions.{extra}"
|
||||
)
|
||||
@@ -181,11 +402,16 @@ def native_runtime_status() -> dict[str, Any]:
|
||||
rt = _NATIVE_RUNTIME or {}
|
||||
return {
|
||||
"native_mcp_transport": is_native_mcp_transport(),
|
||||
"production_native_mcp_transport": is_production_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"),
|
||||
"entrypoint_path": rt.get("entrypoint_path"),
|
||||
"phase": rt.get("phase"),
|
||||
"transport": rt.get("transport"),
|
||||
"mode": rt.get("mode"),
|
||||
"env_sanctioned_alone_insufficient": True,
|
||||
"sanctioned_env": SANCTIONED_DAEMON_ENV,
|
||||
"allow_direct_import_env": ALLOW_DIRECT_IMPORT_ENV,
|
||||
@@ -203,10 +429,18 @@ def runtime_status() -> dict[str, Any]:
|
||||
def mutation_provenance_fields() -> dict[str, Any]:
|
||||
"""Fields to attach to live mutation / review audit records (#695 AC6)."""
|
||||
st = native_runtime_status()
|
||||
transport = "native_mcp" if st["native_mcp_transport"] else "untrusted"
|
||||
if st.get("mode") == _RUNTIME_MODE_TEST and st["native_mcp_transport"]:
|
||||
transport = "test_native_mcp"
|
||||
return {
|
||||
"transport": "native_mcp" if st["native_mcp_transport"] else "untrusted",
|
||||
"transport": transport,
|
||||
"native_mcp_transport": bool(st["native_mcp_transport"]),
|
||||
"production_native_mcp_transport": bool(
|
||||
st.get("production_native_mcp_transport")
|
||||
),
|
||||
"native_runtime_pid": st.get("pid"),
|
||||
"native_token_fingerprint": st.get("token_fingerprint"),
|
||||
"entrypoint": st.get("entrypoint"),
|
||||
"phase": st.get("phase"),
|
||||
"mode": st.get("mode"),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user