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)
4.0 KiB
MCP daemon import and native-transport guard (#558 / #695)
Problem
During deadlock debugging and the PR #694 incident (#695), agents imported
gitea_mcp_server or ran credential helpers from a raw shell / offline helper,
bypassing native MCP transport, preflight purity, and role gates. Contaminated
formal reviews then looked identical to native approvals.
Rule
Mutation auth, keychain fill, and controller quarantine require a production native MCP transport runtime established only by:
- the resolved absolute path of the canonical entrypoint
(
mcp_server.py/gitea_mcp_server.pynext tomcp_daemon_guard.py), and - a live transport bind (
bind_native_mcp_transport(transport="stdio")) immediately beforemcp.run.
Basename-only trust (a renamed file called mcp_server.py), caller-controlled
flags (there is no allow_test_bootstrap), environment variables, stack
frame spoofing, or import-only launch are insufficient.
| Context | Allowed |
|---|---|
| Official IDE-native MCP: resolved canonical entrypoint marks + binds stdio, holds process-local runtime token | yes |
pytest (hermetic unit tests) via is_pytest_runtime() |
yes for unit gates |
install_test_native_runtime() under pytest (test-mode record) |
unit-test transport gates only — never production Gitea mutations |
allow_test_bootstrap=True (removed; must not exist) |
no |
Renamed runner basename mcp_server.py outside package root |
no |
| Import/launch of real entrypoint without transport bind | no |
GITEA_MCP_SANCTIONED_DAEMON=1 alone (no process-local native runtime) |
no (#695) |
GITEA_ALLOW_DIRECT_MCP_IMPORT=1 in LLM sessions |
no — never set; never authorizes mutations (#695 AC1 / PR #701) |
Override GITEA_MCP_SESSION_STATE_DIR mid-session |
no — production bind pins state root; redirect cannot forge independent decision locks (#695 AC2 / PR #701) |
GITEA_ALLOW_KEYCHAIN_CLI=1 in LLM sessions |
no — human operator only |
bare python -c 'import gitea_mcp_server; …' or offline runners |
no |
| keychain fill outside native/pytest | no |
Native runtime is process-local: a random token bound to the daemon PID and transport phase. It is never reconstructed from environment variables, session-state files, caller-controlled flags, or importing internals in a fresh Python process.
Contaminated review quarantine (#695 AC8)
Controller/reconciler/merger profiles may call
gitea_quarantine_contaminated_review with explicit confirmation:
QUARANTINE CONTAMINATED REVIEW <review_id> PR <pr_number>
Quarantine records are durable under the MCP session-state root and are honored by:
gitea_get_pr_review_feedback(quarantined approvals do not authorize merge)gitea_check_pr_eligibilityaction=mergegitea_merge_pr(mutation)- merger lease adoption paths that read
approval_at_current_head
Forensic Gitea reviews and historical comments are never deleted.
STOP after native MCP failure (AC10)
If the native MCP namespace dies (EOF, capability disconnect, session death):
- STOP. State BLOCKED + DIAGNOSE.
- Do not import
gitea_mcp_serverfrom a standalone process. - Do not run
offline_mcp_helper.py,offline_mcp_runner.py,run_quarantine.py, or any offline mutation helper. - Do not set direct-import, keychain-bypass, or raw-token environment variables.
- Reconnect / restart the official MCP daemon; resume only via native tools.
Any further native MCP failure is a hard stop. Do not construct another fallback.
Canonical approval claims (AC7)
Comments that claim approved / ready-to-merge / WHO_IS_NEXT: merger /
MERGE_READY: true must include:
NATIVE_REVIEW_PROOF: transport=native_mcp; …
Claims that cite offline/import helpers are rejected even if a proof line is present.
Operator note
LLM sessions must never set allow-direct-import, allow-keychain-cli, or raw token overrides. Those are human-only escape hatches outside agent workflows.