Files
Gitea-Tools/docs/remote-mcp/threat-model-anchors.json
T
sysadminandClaude Opus 4.8 b3de9c941c docs(remote-mcp): threat model, trust boundaries, and decomposition ruling (Closes #956)
#930 inventoried stdio coupling; nothing stated what the adversary is, what
each boundary protects, or why one process may hold credentials for several
services. This adds that document as child 2 of epic #929.

Adds docs/remote-mcp/threat-model.md covering 10 assets, the 5 adversaries
#956 names, 9 trust boundaries, the data flows between them, a 14-entry
per-boundary credential inventory, and an explicit decomposition ruling.

Findings established from live native evidence at this commit:

- Four prgs roles (reviewer, merger, reconciler, controller) resolve to one
  Gitea account, so separation of duty between approving and landing is
  enforced only by which process a call reaches. The mdcps tenant has no role
  separation at all: author, reviewer, and merger share one account.
- Any one role process can resolve every other role's credential.
  gitea_list_profiles reports "credentials present" for other roles because it
  calls resolve_token on each one.
- The Gitea server reads Jenkins and GlitchTip secrets out of the keychain to
  produce the "authenticated" word in gitea_audit_config's service summaries.
- Jenkins and GlitchTip were already decomposed into separate MCP servers; the
  credential references were left behind in the Gitea configuration.

Ruling D1 forbids a single integration process from holding credentials for
unrelated services, with one time-boxed dual-run exception for the local fleet
that expires with #939. D2 requires separation of duty to be credential-backed,
D3 scopes credential resolution to the request principal, and D4 gives
coordination state its own authority. Every #929 child from 2 through 10 is
mapped to the boundary it implements.

Anchors are enforced rather than asserted. #930's inventory anchors into
gitea_mcp_server.py had already drifted between 7bf4f125 and aad5c8b4 with
nothing detecting it, so this change ships the guard that was missing:
docs/remote-mcp/threat-model-anchors.json declares all 58 anchors with the
substring each must contain, and tests/test_issue_956_threat_model.py fails if
any anchor does not resolve, if the document cites an anchor the fixture does
not cover, or if the structural obligations regress.

Documentation only. No server behavior changes.

Tests:
- tests/test_issue_956_threat_model.py: 17 passed.
- Four sabotage probes confirm the validator is not passing vacuously
  (shifted anchor, undeclared citation, broken count tally, and a blanked
  boundary owner). The last two probes exposed real weaknesses in the checks
  themselves, which were fixed: the section slice now stops at the next
  heading, and boundary ownership is read only from mapping table rows.
- Docs-sensitive sweep (17 modules referencing docs/): 559 passed.
- Full suite: 30 failed, 5799 passed, 6 skipped. All 30 reproduce on a clean
  base worktree at aad5c8b4; branch failures are a strict subset of base
  failures. No production file is modified by this change.

Closes #956

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-28 04:41:26 -04:00

81 lines
5.2 KiB
JSON

{
"_comment": [
"Machine-checkable anchor table for docs/remote-mcp/threat-model.md (#956).",
"Every file:line anchor cited in the threat model must appear here, and the",
"source line at that anchor must contain the 'expect' substring.",
"tests/test_issue_956_threat_model.py enforces both directions, so a refactor",
"that shifts a line number fails the suite instead of silently rotting the",
"document. #930's inventory had no such guard and its gitea_mcp_server.py",
"anchors drifted between 7bf4f125 and aad5c8b4."
],
"generated_against_commit": "aad5c8b42361d380a8eeb07b94b90815e594c2c5",
"anchors": [
{"anchor": "gitea_mcp_server.py:24721", "expect": "bind_native_mcp_transport(transport=\"stdio\")"},
{"anchor": "mcp_daemon_guard.py:45", "expect": "_PRODUCTION_TRANSPORTS = frozenset({\"stdio\"})"},
{"anchor": "mcp_daemon_guard.py:174", "expect": "def bind_native_mcp_transport"},
{"anchor": "irrecoverable_provenance.py:497", "expect": "def assess_transport_for_auth_mint"},
{"anchor": "gitea_mcp_server.py:9129", "expect": "assess_transport_for_auth_mint()"},
{"anchor": "gitea_mcp_server.py:9378", "expect": "assess_transport_for_auth_mint()"},
{"anchor": "mcp_server.py:4", "expect": "Runs over stdio."},
{"anchor": "gitea_mcp_server.py:15412", "expect": "def _is_client_managed_process"},
{"anchor": "gitea_mcp_server.py:15442", "expect": "def _provenance_mutation_block"},
{"anchor": "gitea_mcp_server.py:15450", "expect": "unsupported_manual_launch"},
{"anchor": "gitea_mcp_server.py:19001", "expect": "server_provenance"},
{"anchor": "gitea_mcp_server.py:21442", "expect": "def _check_mcp_runtimes_diagnostics"},
{"anchor": "gitea_mcp_server.py:21462", "expect": "\"ps\", \"-o\", \"pid,lstart,command\""},
{"anchor": "gitea_mcp_server.py:21506", "expect": "\"ps\", \"eww\""},
{"anchor": "gitea_config.py:1172", "expect": "RECOGNIZED_GITEA_ENV_KEYS"},
{"anchor": "gitea_config.py:1233", "expect": "GITEA_CLIENT_MANAGED"},
{"anchor": "gitea_config.py:54", "expect": "ENV_PROFILE = \"GITEA_MCP_PROFILE\""},
{"anchor": "gitea_config.py:97", "expect": "_REVIEW_MERGE_OPS"},
{"anchor": "gitea_config.py:499", "expect": "repository authorization scope"},
{"anchor": "gitea_config.py:956", "expect": "def _keychain_token"},
{"anchor": "gitea_config.py:974", "expect": "def resolve_token"},
{"anchor": "gitea_config.py:1015", "expect": "def keychain_auth"},
{"anchor": "gitea_config.py:294", "expect": "def _validate_identity_auth"},
{"anchor": "mcp_daemon_guard.py:440", "expect": "def assert_keychain_access_allowed"},
{"anchor": "gitea_mcp_server.py:19258", "expect": "def gitea_list_profiles"},
{"anchor": "gitea_mcp_server.py:19309", "expect": "gitea_config.resolve_token(p)"},
{"anchor": "gitea_mcp_server.py:19552", "expect": "def gitea_audit_config"},
{"anchor": "gitea_mcp_server.py:19574", "expect": "service_summaries(config)"},
{"anchor": "gitea_config.py:704", "expect": "def resolve_service"},
{"anchor": "gitea_config.py:837", "expect": "def service_summaries"},
{"anchor": "gitea_config.py:851", "expect": "_keychain_token(auth.get(\"id\"))"},
{"anchor": "gitea_mcp_server.py:17707", "expect": "\"jenkins-mcp\""},
{"anchor": "gitea_mcp_server.py:17713", "expect": "external-mcp"},
{"anchor": "gitea_mcp_server.py:17734", "expect": "\"glitchtip-mcp\""},
{"anchor": "gitea_mcp_server.py:17739", "expect": "external-mcp"},
{"anchor": "mcp_discoverability.py:9", "expect": "EXPECTED_JENKINS_TOOLS"},
{"anchor": "mcp_discoverability.py:17", "expect": "EXPECTED_GLITCHTIP_TOOLS"},
{"anchor": "sentry_incident_bridge.py:36", "expect": "SENTRY_AUTH_TOKEN"},
{"anchor": "sentry_incident_bridge.py:190", "expect": "def resolve_token"},
{"anchor": "sentry_incident_bridge.py:289", "expect": "Authorization"},
{"anchor": "sentry_observability.py:55", "expect": "SENTRY_DSN"},
{"anchor": "master_parity_gate.py:168", "expect": "def capture_startup_parity"},
{"anchor": "master_parity_gate.py:255", "expect": "mutation_safe"},
{"anchor": "gitea_mcp_server.py:19102", "expect": "def gitea_assess_master_parity"},
{"anchor": "gitea_mcp_server.py:190", "expect": "ACTIVE_WORKTREE_ENV"},
{"anchor": "gitea_mcp_server.py:191", "expect": "AUTHOR_WORKTREE_ENV"},
{"anchor": "gitea_mcp_server.py:2348", "expect": "/tmp/gitea_issue_lock.json"},
{"anchor": "gitea_mcp_server.py:10894", "expect": "def gitea_bootstrap_author_issue_worktree"},
{"anchor": "mcp_server.py:10", "expect": "/tmp/mcp_server_stderr.log"},
{"anchor": "issue_lock_store.py:26", "expect": "DEFAULT_LOCK_DIR"},
{"anchor": "issue_lock_store.py:83", "expect": "def session_pointer_path"},
{"anchor": "issue_lock_store.py:98", "expect": "def is_process_alive"},
{"anchor": "mcp_session_state.py:27", "expect": "DEFAULT_STATE_DIR"},
{"anchor": "control_plane_db.py:47", "expect": "DEFAULT_DB_PATH"},
{"anchor": "control_plane_db.py:380", "expect": "mode=0o700"},
{"anchor": "control_plane_db.py:386", "expect": "sqlite3.connect"},
{"anchor": "control_plane_db.py:1145", "expect": "os.getpid()"},
{"anchor": "gitea_mcp_server.py:12801", "expect": "owner_pid_alive"}
]
}