docs(remote-mcp): threat model, trust boundaries, and decomposition ruling (Closes #956) #965

Merged
sysadmin merged 1 commits from docs/issue-956-remote-mcp-threat-model into master 2026-07-28 08:19:45 -05:00
Owner

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 aad5c8b4

  1. Role separation is not credential separation. Four prgs roles — 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.
  2. The mdcps tenant 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.
  3. Any one role process can resolve every other role's credential. gitea_list_profiles called from the author session reports credentials present for the merger, reviewer, reconciler, and every mdcps profile, because it calls resolve_token on each (gitea_mcp_server.py:19309).
  4. The Gitea server reads Jenkins and GlitchTip secrets. Producing the word authenticated in gitea_audit_config's service summaries requires _keychain_token on each service's own reference (gitea_config.py:851).
  5. Jenkins and GlitchTip were already decomposed into separate MCP servers; the credential references were left behind in the Gitea configuration. The correct architecture was chosen and then leaked across.
  6. Sentry is the exception that is not decomposed — an in-process bridge holding an environment-variable token, inherited by every subprocess the server spawns.

The ruling (AC3, AC5)

  • D1 — No cross-service co-residency. A single integration process must not hold, or be able to resolve, credentials for services it does not itself integrate with. Gitea, Jenkins, observability, and database credentials may not share a process.
  • D2 — separation of duty must be credential-backed (reviewer and merger must be distinct accounts).
  • D3 — credential resolution scoped to the request principal.
  • D4 — coordination state gets its own authority; today it is guarded by filesystem permissions alone.

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

# Criterion Where
1 Every credential listed with holder, boundary, blast radius §5, 14 rows
2 Every boundary states what it protects and what crossing requires §3, B1–B9
3 Explicit co-residency ruling with rationale §6, D1
4 Each #929 child 2–10 mapped to its boundary §7
5 Documented decision that one process must not hold other services' credentials §6, D1 + exceptions
Positive test: every claim traceable to an anchor resolving at the reviewed commit fixture + test
Negative test: compromised-client walkthrough, before and after §8

Anchors are enforced, not asserted

#930's inventory cited gitea_mcp_server.py anchors generated at 7bf4f125. By aad5c8b4 they 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.py17 passed.
  • Four sabotage probes confirm the validator is not passing vacuously: a shifted anchor, an undeclared citation, a broken count tally, and a blanked boundary owner each fail the suite, and the suite is green when restored. Two probes exposed real weaknesses in the checks themselves, now fixed — the section slice stops at the next heading (a later walkthrough table was satisfying the mapping's coverage check), and boundary ownership is read only from mapping table rows.
  • Docs-sensitive sweep, all 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. 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-author namespace; gitea_whoami verified jcwalker3 / prgs-author. Base aad5c8b42361d380a8eeb07b94b90815e594c2c5, head b3de9c941c59e773290b4ecbf41856f8cddb1e74. Self-review not permitted.

Closes #956

## 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 `aad5c8b4` 1. **Role separation is not credential separation.** Four `prgs` roles — 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. 2. **The `mdcps` tenant 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. 3. **Any one role process can resolve every other role's credential.** `gitea_list_profiles` called from the author session reports `credentials present` for the merger, reviewer, reconciler, and every `mdcps` profile, because it calls `resolve_token` on each (`gitea_mcp_server.py:19309`). 4. **The Gitea server reads Jenkins and GlitchTip secrets.** Producing the word `authenticated` in `gitea_audit_config`'s service summaries requires `_keychain_token` on each service's own reference (`gitea_config.py:851`). 5. **Jenkins and GlitchTip were already decomposed** into separate MCP servers; the credential *references* were left behind in the Gitea configuration. The correct architecture was chosen and then leaked across. 6. **Sentry is the exception that is not decomposed** — an in-process bridge holding an environment-variable token, inherited by every subprocess the server spawns. ## The ruling (AC3, AC5) - **D1 — No cross-service co-residency.** A single integration process **must not** hold, or be able to resolve, credentials for services it does not itself integrate with. Gitea, Jenkins, observability, and database credentials may not share a process. - **D2** — separation of duty must be credential-backed (reviewer and merger must be distinct accounts). - **D3** — credential resolution scoped to the request principal. - **D4** — coordination state gets its own authority; today it is guarded by filesystem permissions alone. 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 | # | Criterion | Where | | - | --------- | ----- | | 1 | Every credential listed with holder, boundary, blast radius | §5, 14 rows | | 2 | Every boundary states what it protects and what crossing requires | §3, B1–B9 | | 3 | Explicit co-residency ruling with rationale | §6, D1 | | 4 | Each #929 child 2–10 mapped to its boundary | §7 | | 5 | Documented decision that one process must not hold other services' credentials | §6, D1 + exceptions | | — | Positive test: every claim traceable to an anchor resolving at the reviewed commit | fixture + test | | — | Negative test: compromised-client walkthrough, before and after | §8 | ## Anchors are enforced, not asserted #930's inventory cited `gitea_mcp_server.py` anchors generated at `7bf4f125`. By `aad5c8b4` they 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**. - **Four sabotage probes** confirm the validator is not passing vacuously: a shifted anchor, an undeclared citation, a broken count tally, and a blanked boundary owner each fail the suite, and the suite is green when restored. Two probes exposed real weaknesses in the checks themselves, now fixed — the section slice stops at the next heading (a later walkthrough table was satisfying the mapping's coverage check), and boundary ownership is read only from mapping table rows. - Docs-sensitive sweep, all 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. 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-author` namespace; `gitea_whoami` verified `jcwalker3` / `prgs-author`. Base `aad5c8b42361d380a8eeb07b94b90815e594c2c5`, head `b3de9c941c59e773290b4ecbf41856f8cddb1e74`. Self-review not permitted. Closes #956
jcwalker3 added 1 commit 2026-07-28 03:43:36 -05:00
#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]>
Owner

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: b3de9c941c
target_branch: master
target_branch_sha: aad5c8b423
last_activity: 2026-07-28T09:02:52Z
expires_at: 2026-07-28T09:12:52Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74 target_branch: master target_branch_sha: aad5c8b42361d380a8eeb07b94b90815e594c2c5 last_activity: 2026-07-28T09:02:52Z expires_at: 2026-07-28T09:12:52Z blocker: none
Owner

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: b3de9c941c
target_branch: master
target_branch_sha: aad5c8b423
last_activity: 2026-07-28T09:24:41Z
expires_at: 2026-07-28T09:34:41Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74 target_branch: master target_branch_sha: aad5c8b42361d380a8eeb07b94b90815e594c2c5 last_activity: 2026-07-28T09:24:41Z expires_at: 2026-07-28T09:34:41Z blocker: none
sysadmin approved these changes 2026-07-28 04:26:22 -05:00
sysadmin left a comment
Owner

Canonical PR State

STATE: reviewed-approved
WHO_IS_NEXT: merger
NEXT_ACTION: Merge PR #965 into master at head b3de9c941c through the native gitea-merger namespace, then close #956 and delete branch docs/issue-956-remote-mcp-threat-model.
NEXT_PROMPT:

Act as merger in the native gitea-merger namespace (profile prgs-merger, remote prgs, org Scaled-Tech-Consulting, repo Gitea-Tools). Merge PR #965 at head b3de9c941c59e773290b4ecbf41856f8cddb1e74 onto base master aad5c8b42361d380a8eeb07b94b90815e594c2c5. Preflight: gitea_whoami, then gitea_resolve_task_capability with task merge_pr, then acquire the merger PR lease with acquire_merger_pr_lease. Pass branch_protection_requires_current_base=false explicitly. Confirm the approval by sysadmin is visible at the current head before merging. After merging, close #956, delete the source branch, and report the merge commit SHA.

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: b3de9c941c
REVIEW_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 aad5c8b4 in 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74
Base: aad5c8b42361d380a8eeb07b94b90815e594c2c5 (merge-base verified; live master at review time)
Reviewer: sysadmin / prgs-reviewer — distinct from author jcwalker3.

NATIVE_REVIEW_PROOF: native gitea-reviewer MCP namespace over stdio, daemon pid 36679, profile lock prgs-reviewer, identity sysadmin, host gitea.prgs.cc, repo Scaled-Tech-Consulting/Gitea-Tools; reviewer lease 36679-1372c21d2f4e (comments 18191, 18193) held at the reviewed head; parity in_parity=true, live_stale=false, restart_required=false, mutation_safe=true at aad5c8b42361; capability review_pr resolved green; workflow proof 263d0a6cb8a6.

Scope

Three added files, 806 insertions, zero modified files. git diff --name-status against 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

  • AC1 14 credential rows (CR1–CR14), each stating holder, boundary, and blast radius.
  • AC2 9 boundary rows (B1–B9), each stating what it protects, what crossing requires today, and what it must require remotely.
  • AC3 §6 D1 rules explicitly on co-residency across Gitea, Jenkins, GlitchTip, Sentry, observability, and database credentials, with rationale.
  • AC4 §7 maps epic children 2–10 (#931–#939) to boundaries; B1–B9 each have at least one owning child, B7 deliberately owned only by #933. No contradictory ownership.
  • AC5 D1 plus one time-boxed exception, scoped to the local stdio fleet, excluded from the remote endpoint and from Sentry, expiring with #939.
  • Positive test anchor traceability is enforced, see below.
  • Negative test §8 walkthrough separates current behavior from intended behavior, and states plainly that the migration does not constrain ADV2.

Independent verification, not taken from the handoff

Findings 1–4 reproduced live from the reviewer namespace:

  • gitea_audit_config confirms prgs-reviewer, prgs-merger, prgs-reconciler, prgs-controller all resolve to sysadmin, and mdcps-author / mdcps-reviewer / mdcps-merger all to 913443 — Findings 1 and 2.
  • gitea_list_profiles reports credentials present for every other role's profile — Finding 3, reproduced from a reviewer session rather than an author one.
  • gitea_audit_config emits MDCPS Jenkins: enabled, read-only, authenticated and MDCPS GlitchTip: enabled, read-only, authenticated from the Gitea process — Finding 4. B7 does not exist.
  • Exactly 8 role credentials configured, matching A3 and CR1–CR8.

Drift claim verified: docs/remote-mcp/coupling-inventory.md does cite gitea_mcp_server.py:23750 and :14588 at 7bf4f125; 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_commit is aad5c8b4, 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 at b3de9c94.

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

  1. Two structure tests are weaker than their names suggest. test_credential_rows_declare_holder_boundary_and_blast_radius and test_every_boundary_states_what_it_protects_and_what_crossing_requires assert 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.
  2. Anchor coverage is enforced in one direction only. The suite fails when the document cites an anchor the fixture omits, but not when the fixture declares an anchor the document never cites. gitea_mcp_server.py:15442 is declared and cited nowhere.
  3. §8 states ADV3 is diffused across every child rather than owned. Against epic #929's canonical ten-child table that reads correctly, though #957 now covers request-tuple authorization and may deserve a cross-reference.
## Canonical PR State STATE: reviewed-approved WHO_IS_NEXT: merger NEXT_ACTION: Merge PR #965 into master at head b3de9c941c59e773290b4ecbf41856f8cddb1e74 through the native gitea-merger namespace, then close #956 and delete branch docs/issue-956-remote-mcp-threat-model. NEXT_PROMPT: ```text Act as merger in the native gitea-merger namespace (profile prgs-merger, remote prgs, org Scaled-Tech-Consulting, repo Gitea-Tools). Merge PR #965 at head b3de9c941c59e773290b4ecbf41856f8cddb1e74 onto base master aad5c8b42361d380a8eeb07b94b90815e594c2c5. Preflight: gitea_whoami, then gitea_resolve_task_capability with task merge_pr, then acquire the merger PR lease with acquire_merger_pr_lease. Pass branch_protection_requires_current_base=false explicitly. Confirm the approval by sysadmin is visible at the current head before merging. After merging, close #956, delete the source branch, and report the merge commit SHA. ``` 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74 REVIEW_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 aad5c8b4 in 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:** `b3de9c941c59e773290b4ecbf41856f8cddb1e74` **Base:** `aad5c8b42361d380a8eeb07b94b90815e594c2c5` (merge-base verified; live `master` at review time) **Reviewer:** `sysadmin` / `prgs-reviewer` — distinct from author `jcwalker3`. NATIVE_REVIEW_PROOF: native `gitea-reviewer` MCP namespace over stdio, daemon pid 36679, profile lock `prgs-reviewer`, identity `sysadmin`, host `gitea.prgs.cc`, repo `Scaled-Tech-Consulting/Gitea-Tools`; reviewer lease `36679-1372c21d2f4e` (comments 18191, 18193) held at the reviewed head; parity `in_parity=true`, `live_stale=false`, `restart_required=false`, `mutation_safe=true` at `aad5c8b42361`; capability `review_pr` resolved green; workflow proof `263d0a6cb8a6`. ### Scope Three added files, 806 insertions, zero modified files. `git diff --name-status` against 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 - **AC1** 14 credential rows (CR1–CR14), each stating holder, boundary, and blast radius. - **AC2** 9 boundary rows (B1–B9), each stating what it protects, what crossing requires today, and what it must require remotely. - **AC3** §6 D1 rules explicitly on co-residency across Gitea, Jenkins, GlitchTip, Sentry, observability, and database credentials, with rationale. - **AC4** §7 maps epic children 2–10 (#931–#939) to boundaries; B1–B9 each have at least one owning child, B7 deliberately owned only by #933. No contradictory ownership. - **AC5** D1 plus one time-boxed exception, scoped to the local stdio fleet, excluded from the remote endpoint and from Sentry, expiring with #939. - **Positive test** anchor traceability is enforced, see below. - **Negative test** §8 walkthrough separates current behavior from intended behavior, and states plainly that the migration does not constrain ADV2. ### Independent verification, not taken from the handoff Findings 1–4 reproduced live from the **reviewer** namespace: - `gitea_audit_config` confirms `prgs-reviewer`, `prgs-merger`, `prgs-reconciler`, `prgs-controller` all resolve to `sysadmin`, and `mdcps-author` / `mdcps-reviewer` / `mdcps-merger` all to `913443` — Findings 1 and 2. - `gitea_list_profiles` reports `credentials present` for every other role's profile — Finding 3, reproduced from a reviewer session rather than an author one. - `gitea_audit_config` emits `MDCPS Jenkins: enabled, read-only, authenticated` and `MDCPS GlitchTip: enabled, read-only, authenticated` from the Gitea process — Finding 4. B7 does not exist. - Exactly 8 role credentials configured, matching A3 and CR1–CR8. Drift claim verified: `docs/remote-mcp/coupling-inventory.md` does cite `gitea_mcp_server.py:23750` and `:14588` at `7bf4f125`; 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_commit` is `aad5c8b4`, 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 at `b3de9c94`. ### 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 1. Two structure tests are weaker than their names suggest. `test_credential_rows_declare_holder_boundary_and_blast_radius` and `test_every_boundary_states_what_it_protects_and_what_crossing_requires` assert 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. 2. Anchor coverage is enforced in one direction only. The suite fails when the document cites an anchor the fixture omits, but not when the fixture declares an anchor the document never cites. `gitea_mcp_server.py:15442` is declared and cited nowhere. 3. §8 states ADV3 is diffused across every child rather than owned. Against epic #929's canonical ten-child table that reads correctly, though #957 now covers request-tuple authorization and may deserve a cross-reference.
Owner

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: b3de9c941c
target_branch: master
target_branch_sha: aad5c8b423
last_activity: 2026-07-28T09:26:51Z
expires_at: 2026-07-28T09:36:51Z
blocker: manual-release

<!-- mcp-review-lease:v1 --> 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74 target_branch: master target_branch_sha: aad5c8b42361d380a8eeb07b94b90815e594c2c5 last_activity: 2026-07-28T09:26:51Z expires_at: 2026-07-28T09:36:51Z blocker: manual-release
Owner

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: b3de9c941c
target_branch: master
target_branch_sha: aad5c8b423
last_activity: 2026-07-28T13:18:29Z
expires_at: 2026-07-28T13:28:29Z
blocker: none

<!-- mcp-review-lease:v1 --> 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: b3de9c941c59e773290b4ecbf41856f8cddb1e74 target_branch: master target_branch_sha: aad5c8b42361d380a8eeb07b94b90815e594c2c5 last_activity: 2026-07-28T13:18:29Z expires_at: 2026-07-28T13:28:29Z blocker: none
sysadmin merged commit 9b80e75ca3 into master 2026-07-28 08:19:45 -05:00
Owner

Stale #332 review-decision lock cleanup (#594)

Status: APPLIED

Manual deletion of session-state files is not the workflow.
This path only clears a lock when the referenced PR is merged/closed.

## Stale #332 review-decision lock cleanup (#594) Status: **APPLIED** - actor: `sysadmin` - profile: `prgs-merger` - timestamp: `2026-07-28T13:19:50.073936+00:00` - last terminal: `approve` on PR #965 - PR state: `closed` (merged=True) - merge_commit_sha: `9b80e75ca3f441fec2fb077a1b5f874faa0912e2` - prior live_mutations_count: `1` - prior profile_identity: `prgs-reviewer` Manual deletion of session-state files is **not** the workflow. This path only clears a lock when the referenced PR is merged/closed.
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Scaled-Tech-Consulting/Gitea-Tools#965