docs(remote-mcp): threat model, trust boundaries, and decomposition ruling (Closes #956) #965
Labels
Clear labels
allocator
anti-stomp
architecture
bug
chore
codex
concurrency
contamination
control-plane
dashboard
database
design
documentation
enhancement
gitea
glitchtip
important
incident
incident-bridge
integration
jenkins
labels
leases
mcp
mcp-health
mcp-menu
multi-project
mutating
nice-to-have
observability
portability
preflight
protected-branch
queue
read-only
reconnect
recovery
refactor
release
reliability
resumable-review
reviewer
roadmap
safety
security
self-hosted
sentry
stale-runtime
status:blocked
status:in-progress
status:pr-open
status:ready
terminal-lock
testing
tracker
type:bug
type:feature
type:feature
type:guardrail
visibility
workflow
workflow-hardening
workflow-hardening
Controller-owned work allocator
Prevent concurrent LLM session stomping
Architecture / structural design
OpenAI Codex client / workflow session surface
Concurrent session safety
Workflow or session contamination incident
MCP control-plane coordination and allocation authority
MCP operational dashboard/queue view
Internal coordination storage (SQLite/Postgres)
Design / investigation, no implementation
Docs / runbooks
New feature or improvement
Gitea MCP workflow
GlitchTip integration
Operational or process incident requiring durable audit trail
Sentry-to-Gitea incident bridging
Integration testing
Jenkins integration
Label taxonomy management
Lease adopt/release/expire lifecycle
MCP server / tooling
MCP namespace and runtime health
MCP menu surface
Work spanning multiple monitoring projects or Gitea repos
Mutating action; requires gating
Observability, metrics, traces, error reporting
Cross-platform / portability
Shared preflight gates before mutation
Protected branch / stable-branch policy concern
Work queue visibility and allocation
Read-only, no mutation
MCP client reconnect/reload recovery path
Recovery paths for stale/foreign leases
Code refactor / restructure
Release / versioning
Reliability / failure handling
Persist and resume prepared review verdicts across sessions
Reviewer workflow tooling
Roadmap / umbrella issue
Safety rails and fail-closed mutation guards
Security / trust boundary
Self-hosted infrastructure integration
Sentry error monitoring integration
Stale backend daemon / runtime-vs-master parity failures
Issue is blocked
Issue is being worked on
Issue has an open pull request
Issue is ready for work
Terminal review lock (#332) path
Tests / test coverage
Issue tracker hygiene / meta
Bug or defect
Feature or enhancement
Feature or enhancement
Safety gate or guardrail
Workflow state visibility for LLMs/operators
Cross-tool workflow
LLM workflow coordination hardening
LLM workflow coordination hardening
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: Scaled-Tech-Consulting/Gitea-Tools#965
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Implements #956 (child 2 of epic #929, cross-linked to #955). #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.
Documentation only. No server behavior changes. Three new files, zero modified files.
docs/remote-mcp/threat-model.md— 10 assets, the 5 adversaries #956 names, 9 trust boundaries, data flows, a 14-entry credential inventory, the decomposition ruling, the child mapping, and the compromised-client walkthrough.docs/remote-mcp/threat-model-anchors.json— 58 anchors, each with the substring it must resolve to.tests/test_issue_956_threat_model.py— enforces both.Findings, from live native evidence at
aad5c8b4prgsroles — reviewer, merger, reconciler, controller — resolve to one Gitea account. A stolen reviewer credential is a merger credential, so separation of duty between approving and landing is enforced only by which process a call reaches, and not at all by the forge.mdcpstenant has no role separation at all. Author, reviewer, and merger share one account. The self-review check that would refuse runs on our side of the network boundary; an attacker holding the token calls the API, not our tools.gitea_list_profilescalled from the author session reportscredentials presentfor the merger, reviewer, reconciler, and everymdcpsprofile, because it callsresolve_tokenon each (gitea_mcp_server.py:19309).authenticatedingitea_audit_config's service summaries requires_keychain_tokenon each service's own reference (gitea_config.py:851).The ruling (AC3, AC5)
One exception, explicitly justified and time-boxed: the local stdio fleet may keep resolving Jenkins and GlitchTip references through the dual-run window, expiring with #939. It does not extend to the remote endpoint or to Sentry. No exception to D2, D3, or D4.
Acceptance criteria
Anchors are enforced, not asserted
#930's inventory cited
gitea_mcp_server.pyanchors generated at7bf4f125. Byaad5c8b4they had drifted — its transport bind at line 23750 now lives at line 24721, and its provenance anchor lands in a different function. Nothing failed, because nothing checked. This change ships the missing guard: the test fails if any anchor does not resolve, if the document cites an anchor the fixture does not declare, or if the structural obligations regress.Test evidence
tests/test_issue_956_threat_model.py— 17 passed.docs/— 559 passed.aad5c8b4; branch failures are a strict subset of base failures. The base additionally fails one httpx2 test that passes here, a subset-vs-full-suite isolation artifact in that pre-existing module. No production file is modified by this change.Scope
Stays inside #956. No implementation, no secret-store choice, no role-taxonomy change. Does not begin #957 or #958, and does not touch #949 or its preserved state.
Handoff
STATE: ready-for-review
WHO_IS_NEXT: reviewer
NEXT_ACTION: Review against #956 acceptance criteria. The document's own claims are machine-checked — run
PYTHONPATH=. pytest tests/test_issue_956_threat_model.py, then spot-check any five anchors against the fixture. Findings 3 and 4 are reproducible live from the author namespace.Authored through the native
gitea-authornamespace;gitea_whoamiverifiedjcwalker3/prgs-author. Baseaad5c8b42361d380a8eeb07b94b90815e594c2c5, headb3de9c941c59e773290b4ecbf41856f8cddb1e74. Self-review not permitted.Closes #956
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #965
issue: #956
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 36679-1372c21d2f4e
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr965-b3de9c94
phase: claimed
candidate_head:
b3de9c941ctarget_branch: master
target_branch_sha:
aad5c8b423last_activity: 2026-07-28T09:02:52Z
expires_at: 2026-07-28T09:12:52Z
blocker: none
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #965
issue: #956
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 36679-1372c21d2f4e
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr965-b3de9c94
phase: validated
candidate_head:
b3de9c941ctarget_branch: master
target_branch_sha:
aad5c8b423last_activity: 2026-07-28T09:24:41Z
expires_at: 2026-07-28T09:34:41Z
blocker: none
Canonical PR State
STATE: reviewed-approved
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #965 into master at head
b3de9c941cthrough the native gitea-merger namespace, then close #956 and delete branch docs/issue-956-remote-mcp-threat-model.NEXT_PROMPT:
WHAT_HAPPENED: Independent reviewer review of a documentation-only change adding the remote-MCP threat model, its anchor fixture, and the test that enforces both. Diff, acceptance criteria, anchors, secret exposure, test validity, and the full suite were verified independently at the reviewed head. All five #956 acceptance criteria are satisfied and no actionable blocker remains.
WHY: The document satisfies every #956 acceptance criterion, its anchor traceability guard is genuinely load-bearing under adversarial probing, its four substantive findings reproduce live from the reviewer namespace, no secret value is committed, and the failing-test identifier set is byte-identical to the base revision, so the change introduces zero regressions.
ISSUE: #956
HEAD_SHA:
b3de9c941cREVIEW_STATUS: APPROVED
MERGE_READY: yes
BLOCKERS: none
VALIDATION: tests/test_issue_956_threat_model.py 17 passed; 17 modules referencing docs/ 559 passed with 192 subtests; collection 5835 tests, no errors; full suite at head 30 failed, 5799 passed, 6 skipped; full suite at base
aad5c8b4in a clean branches worktree 30 failed, 5782 passed, 6 skipped; failing node-ID sets byte-identical, pass delta exactly the 17 added tests.LAST_UPDATED_BY: sysadmin / prgs-reviewer
APPROVE — PR #965 (#956)
Reviewed head:
b3de9c941c59e773290b4ecbf41856f8cddb1e74Base:
aad5c8b42361d380a8eeb07b94b90815e594c2c5(merge-base verified; livemasterat review time)Reviewer:
sysadmin/prgs-reviewer— distinct from authorjcwalker3.NATIVE_REVIEW_PROOF: native
gitea-reviewerMCP namespace over stdio, daemon pid 36679, profile lockprgs-reviewer, identitysysadmin, hostgitea.prgs.cc, repoScaled-Tech-Consulting/Gitea-Tools; reviewer lease36679-1372c21d2f4e(comments 18191, 18193) held at the reviewed head; parityin_parity=true,live_stale=false,restart_required=false,mutation_safe=trueataad5c8b42361; capabilityreview_prresolved green; workflow proof263d0a6cb8a6.Scope
Three added files, 806 insertions, zero modified files.
git diff --name-statusagainst the base yields only additions:docs/remote-mcp/threat-model.md,docs/remote-mcp/threat-model-anchors.json,tests/test_issue_956_threat_model.py. No unrelated or hidden change.Acceptance criteria — all met
Independent verification, not taken from the handoff
Findings 1–4 reproduced live from the reviewer namespace:
gitea_audit_configconfirmsprgs-reviewer,prgs-merger,prgs-reconciler,prgs-controllerall resolve tosysadmin, andmdcps-author/mdcps-reviewer/mdcps-mergerall to913443— Findings 1 and 2.gitea_list_profilesreportscredentials presentfor every other role's profile — Finding 3, reproduced from a reviewer session rather than an author one.gitea_audit_configemitsMDCPS Jenkins: enabled, read-only, authenticatedandMDCPS GlitchTip: enabled, read-only, authenticatedfrom the Gitea process — Finding 4. B7 does not exist.Drift claim verified:
docs/remote-mcp/coupling-inventory.mddoes citegitea_mcp_server.py:23750and:14588at7bf4f125; at this head those lines hold unrelated lease-listing and query-parameter code. The stated justification for this change is accurate.Five anchors spot-checked by hand at the reviewed head —
gitea_config.py:851,gitea_mcp_server.py:19309,sentry_incident_bridge.py:190,control_plane_db.py:380,mcp_daemon_guard.py:440— all resolve to their declared substrings.generated_against_commitisaad5c8b4, the base rather than the head. Not misleading: this change modifies no source file, so all 12 anchored modules are byte-identical at both commits, and the suite resolves every anchor while checked out atb3de9c94.Test validity — probed independently
I rebuilt the suite in a sandbox and ran my own mutations rather than relying on the reported probes. Correctly caught: a drifted anchor line, a citation the fixture does not declare, a broken credential tally, a mapping-table row losing its boundary owner while the prose summary retains it, a removed epic child, and a duplicated credential identifier. The anchor guard — the substantive contribution here — is genuinely load-bearing.
Secret exposure
No token, secret value, DSN, keychain identifier, endpoint URL, host, or IP appears in any added file. Only forge usernames appear, which is the substance of Findings 1 and 2.
Non-blocking follow-ups
test_credential_rows_declare_holder_boundary_and_blast_radiusandtest_every_boundary_states_what_it_protects_and_what_crossing_requiresassert only that column headers exist. I emptied CR9's holder, boundary, and blast-radius cells, and separately emptied B7's protects and crossing cells; the suite stayed green both times. The document is correct today, so no criterion is unmet, but these two guards will not catch a future row being hollowed out.gitea_mcp_server.py:15442is declared and cited nowhere.repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #965
issue: #956
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 36679-1372c21d2f4e
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr965-b3de9c94
phase: released
candidate_head:
b3de9c941ctarget_branch: master
target_branch_sha:
aad5c8b423last_activity: 2026-07-28T09:26:51Z
expires_at: 2026-07-28T09:36:51Z
blocker: manual-release
repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #965
issue: #956
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 75732-ccecbbcb1c37
worktree: /Users/jasonwalker/Development/Gitea-Tools
phase: claimed
candidate_head:
b3de9c941ctarget_branch: master
target_branch_sha:
aad5c8b423last_activity: 2026-07-28T13:18:29Z
expires_at: 2026-07-28T13:28:29Z
blocker: none
Stale #332 review-decision lock cleanup (#594)
Status: APPLIED
sysadminprgs-merger2026-07-28T13:19:50.073936+00:00approveon PR docs(remote-mcp): threat model, trust boundaries, and decomposition ruling (Closes #956) (#965)closed(merged=True)9b80e75ca3f441fec2fb077a1b5f874faa0912e21prgs-reviewerManual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.