feat(guard): block direct stable-branch pushes from MCP workflow sessions (Closes #671) #677

Merged
sysadmin merged 1 commits from fix/issue-671-block-stable-branch-push into master 2026-07-11 20:05:45 -05:00
Owner

Summary

Closes #671. Prevention hardening for the #670 incident (a bare direct-to-master commit 2fa97c26 and a PR #654 merger git push prgs master attempt). Worker sessions must never publish stable branches directly; stable branches advance only through sanctioned Gitea merge tooling or an explicitly authorized reconciler path.

This is the #670 AC5 hardening. It does not decide accept/revert of 2fa97c26 (controller owns that on #670).

What changed

New pure module stable_branch_push_guard.py (mirrors author_proofs / root_checkout_guard — no git/network/state calls):

  • classify_push_command — detects shell git push <remote> master/main/dev/develop/development variants: refspecs (HEAD:master, +refs/heads/x:refs/heads/master), --force, --delete / :master, and --dry-run/-n no-op probes (a dry-run still proves intent and contaminates).
  • assess_root_checkout_local_commit — detects control-checkout commits not carried by an issue feature branch (branches/ worktrees exempt).
  • redact_command — strips URL userinfo and token assignments before anything is logged/stored.
  • build_contamination_record + assess_contamination_gate — durable marker shape and fail-closed gate (reconciler-exempt; comment_issue/lock_issue stay allowed so a contaminated worker can post its audit comment and hand off).

Server wiring (gitea_mcp_server.py, mcp_session_state.py):

  • KIND_STABLE_BRANCH_CONTAMINATION durable session marker, keyed per profile identity.
  • _enforce_stable_branch_contamination_gate wired into verify_preflight_purity, so review/merge/close/completion mutations fail closed while contaminated.
  • gitea_record_stable_branch_push_attempt — classify a proposed push and mark on detection.
  • gitea_audit_stable_branch_contamination — reconciler-only inspect/clear; a worker session can never self-clear.

Docsskills/llm-project-workflow/SKILL.md: universal rule, a covered blocker class, and a dedicated "Stable Branch Push Protection" section documenting that worker sessions must never push stable branches directly.

Non-goals honored

  • Sanctioned gitea_merge_pr / Gitea API merge is not a push and is never blocked.
  • git fetch / git pull --ff-only of master into the control checkout stays allowed.
  • Feature-branch pushes to non-stable refs stay allowed (no false blocks).

Validation

venv/bin/python -m pytest tests/test_stable_branch_push_guard.py -q          # 37 passed
venv/bin/python -m pytest tests/test_stable_branch_contamination_server.py -q # 14 passed
venv/bin/python -m pytest -q                                                  # 2596 passed, 6 skipped
py_compile gitea_mcp_server.py stable_branch_push_guard.py mcp_session_state.py  # ok
git diff --check                                                              # clean

AC5 coverage: no-op dry-run push, real direct push, sanctioned Gitea merge, fetch-only, root-checkout local commit; plus feature-branch push allowed, gate block/reconciler-exempt, and redaction.

LLM Handoff Metadata

  • LLM-Role: author
  • Authenticated-Gitea-User: jcwalker3
  • MCP-Profile: prgs-author
  • Branch: fix/issue-671-block-stable-branch-push
  • Head: 5933d87647
  • Worktree: branches/fix-issue-671-block-stable-branch-push
  • Links: #670 (incident / AC5), #630 (process-kill contamination class — shared machinery pattern)
  • Self-review allowed: no
## Summary Closes #671. Prevention hardening for the #670 incident (a bare direct-to-master commit `2fa97c26` and a PR #654 merger `git push prgs master` attempt). Worker sessions must never publish stable branches directly; stable branches advance only through sanctioned Gitea merge tooling or an explicitly authorized reconciler path. This is the #670 AC5 hardening. It does not decide accept/revert of `2fa97c26` (controller owns that on #670). ## What changed **New pure module `stable_branch_push_guard.py`** (mirrors `author_proofs` / `root_checkout_guard` — no git/network/state calls): - `classify_push_command` — detects shell `git push <remote> master`/`main`/`dev`/`develop`/`development` variants: refspecs (`HEAD:master`, `+refs/heads/x:refs/heads/master`), `--force`, `--delete` / `:master`, and `--dry-run`/`-n` no-op probes (a dry-run still proves intent and contaminates). - `assess_root_checkout_local_commit` — detects control-checkout commits not carried by an issue feature branch (`branches/` worktrees exempt). - `redact_command` — strips URL userinfo and token assignments before anything is logged/stored. - `build_contamination_record` + `assess_contamination_gate` — durable marker shape and fail-closed gate (reconciler-exempt; `comment_issue`/`lock_issue` stay allowed so a contaminated worker can post its audit comment and hand off). **Server wiring** (`gitea_mcp_server.py`, `mcp_session_state.py`): - `KIND_STABLE_BRANCH_CONTAMINATION` durable session marker, keyed per profile identity. - `_enforce_stable_branch_contamination_gate` wired into `verify_preflight_purity`, so review/merge/close/completion mutations fail closed while contaminated. - `gitea_record_stable_branch_push_attempt` — classify a proposed push and mark on detection. - `gitea_audit_stable_branch_contamination` — reconciler-only inspect/clear; a worker session can never self-clear. **Docs** — `skills/llm-project-workflow/SKILL.md`: universal rule, a covered blocker class, and a dedicated "Stable Branch Push Protection" section documenting that worker sessions must never push stable branches directly. ## Non-goals honored - Sanctioned `gitea_merge_pr` / Gitea API merge is not a push and is never blocked. - `git fetch` / `git pull --ff-only` of master into the control checkout stays allowed. - Feature-branch pushes to non-stable refs stay allowed (no false blocks). ## Validation ``` venv/bin/python -m pytest tests/test_stable_branch_push_guard.py -q # 37 passed venv/bin/python -m pytest tests/test_stable_branch_contamination_server.py -q # 14 passed venv/bin/python -m pytest -q # 2596 passed, 6 skipped py_compile gitea_mcp_server.py stable_branch_push_guard.py mcp_session_state.py # ok git diff --check # clean ``` AC5 coverage: no-op dry-run push, real direct push, sanctioned Gitea merge, fetch-only, root-checkout local commit; plus feature-branch push allowed, gate block/reconciler-exempt, and redaction. ## LLM Handoff Metadata - LLM-Role: author - Authenticated-Gitea-User: jcwalker3 - MCP-Profile: prgs-author - Branch: fix/issue-671-block-stable-branch-push - Head: 5933d87647656643a67a50331c4c7b06ea751dad - Worktree: branches/fix-issue-671-block-stable-branch-push - Links: #670 (incident / AC5), #630 (process-kill contamination class — shared machinery pattern) - Self-review allowed: no
jcwalker3 added 1 commit 2026-07-11 19:09:24 -05:00
Prevention hardening for the #670 incident (bare direct-to-master commit
2fa97c26 and the PR #654 merger stable-branch push attempt). Worker sessions
must never publish stable branches directly; stable updates land only through
sanctioned Gitea merge tooling or an authorized reconciler path.

New pure module stable_branch_push_guard.py:
- classify_push_command: detects git push <remote> <stable-ref> equivalents
  including refspecs (HEAD:<ref>, +refs/heads/x:refs/heads/<ref>), --force,
  --delete/:<ref>, and --dry-run/-n no-op probes (dry-run still proves intent).
  Fetch/pull and feature-branch pushes are never flagged; sanctioned
  gitea_merge_pr / API merge is not a push.
- assess_root_checkout_local_commit: detects control-checkout commits not on an
  issue feature branch (branches/ worktrees exempt).
- redact_command: strips URL userinfo/token assignments before logging.
- build_contamination_record + assess_contamination_gate: durable marker shape
  and fail-closed gate (reconciler-exempt; comment/lock stay allowed for handoff).

Server wiring (gitea_mcp_server.py, mcp_session_state.py):
- KIND_STABLE_BRANCH_CONTAMINATION durable session marker (per profile identity).
- _enforce_stable_branch_contamination_gate wired into verify_preflight_purity
  so review/merge/close/completion mutations fail closed while contaminated.
- gitea_record_stable_branch_push_attempt: classify + mark on detection.
- gitea_audit_stable_branch_contamination: reconciler-only inspect/clear; a
  worker session can never self-clear.

Tests (AC5): no-op dry-run push, real direct push, sanctioned Gitea merge,
fetch-only, root-checkout local commit, feature-branch push allowed, gate
block/reconciler-exempt, redaction. 51 new tests; full suite 2596 passed.

Docs: llm-project-workflow SKILL.md — universal rule, blocker class, and a
dedicated "Stable Branch Push Protection" section (worker sessions must never
push stable branches directly).

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
Owner

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #677
issue: #671
reviewer_identity: sysadmin
profile: prgs-reviewer
session_id: 4979-b6d4f1861a70
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-677
phase: claimed
candidate_head: 5933d87647
target_branch: master
target_branch_sha: bee24e2b10
last_activity: 2026-07-12T00:27:36Z
expires_at: 2026-07-12T02:27:36Z
blocker: none

<!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #677 issue: #671 reviewer_identity: sysadmin profile: prgs-reviewer session_id: 4979-b6d4f1861a70 worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-677 phase: claimed candidate_head: 5933d87647656643a67a50331c4c7b06ea751dad target_branch: master target_branch_sha: bee24e2b102dbdc201932d2e5397a7d9e3be5fc5 last_activity: 2026-07-12T00:27:36Z expires_at: 2026-07-12T02:27:36Z blocker: none
sysadmin approved these changes 2026-07-11 19:29:50 -05:00
sysadmin left a comment
Owner

Review verdict: APPROVE

PR #677 fully satisfies Issue #671 (block direct stable-branch pushes from MCP workflow sessions — #670 AC5 hardening). Reviewed at head 5933d87647656643a67a50331c4c7b06ea751dad in an isolated worktree; all validation green against current prgs/master baseline (bee24e2).

Scope

6 files, all additive (+1295 / -0). No unrelated changes. git diff --check clean.
stable_branch_push_guard.py (new pure module), gitea_mcp_server.py, mcp_session_state.py, skills/llm-project-workflow/SKILL.md, and two test files.

Acceptance criteria — all met

  • AC1 detect direct stable-branch pushes: classify_push_command covers git push <remote> master, refspecs (HEAD:master, +refs/heads/x:refs/heads/master), --force/--force-with-lease, delete (--delete, :master), --dry-run/-n no-op probes (dry-run still proves_intent), and compound commands. Stable set = author_proofs.PROTECTED_BRANCHES so the two guards cannot drift.
  • AC2 root/control-checkout local commit: assess_root_checkout_local_commit; branches/ worktrees exempt; control-checkout HEAD advanced past prgs/master flagged; missing state reported unknown (positive-evidence only).
  • AC3 contamination marker: durable KIND_STABLE_BRANCH_CONTAMINATION, keyed per profile identity; command summary redacted via redact_command (URL userinfo, TOKEN=/PASSWORD=, bearer) — secrets never persist.
  • AC4 fail-closed gate: _enforce_stable_branch_contamination_gate wired into verify_preflight_purity; gated set covers review/merge/close/submit/complete; comment_issue/lock_issue stay allowed for audit handoff; reconciler exempt.
  • AC5 tests: 37 + 14 cover no-op dry-run, real direct push, sanctioned Gitea merge (non-push), fetch-only, root-checkout commit, feature-branch allowed, gate block, reconciler-exempt, redaction.
  • AC6 docs: SKILL.md universal rule + BLOCKED+DIAGNOSE blocker class + dedicated "Stable Branch Push Protection" section.

Non-goals honored

Sanctioned gitea_merge_pr / API merge not blocked (not a push); git fetch / pull --ff-only allowed; feature-branch pushes allowed — all asserted by tests. Reconciler-only clear enforced (role != "reconciler" → refused; worker cannot self-clear).

Canonical PR State

STATE: reviewed-approved
WHO_IS_NEXT: merger
NEXT_ACTION: Merger merges PR #677 via sanctioned Gitea path on explicit operator authorization, then closes #671.
NEXT_PROMPT:

Role: MERGER. Repo prgs/Scaled-Tech-Consulting/Gitea-Tools. Merge PR #677 (Issue #671). Adopt reviewer lease (session 4979-b6d4f1861a70), re-pin live head 5933d87647656643a67a50331c4c7b06ea751dad, confirm open+mergeable+APPROVED at that head, merge via gitea_merge_pr ONLY on explicit operator authorization. No direct stable-branch push. Then close #671.

WHAT_HAPPENED: Reviewer approved PR #677 at head 5933d87 in isolated worktree branches/review-pr-677; full suite 2596 passed / 6 skipped; scope additive-only and matches #671.
WHY: All six acceptance criteria (detection, root-checkout detection, contamination marker, fail-closed gate, tests, docs) are satisfied; redaction and reconciler-only clear verified; non-goals honored; no unrelated changes.
ISSUE: 671
HEAD_SHA: 5933d87647
REVIEW_STATUS: APPROVED
MERGE_READY: yes (pending merger action + explicit operator authorization)
BLOCKERS: none
VALIDATION: git diff --check clean; test_stable_branch_push_guard.py 37 passed; test_stable_branch_contamination_server.py 14 passed; preflight/session/purity/contamination subset 60 passed; full suite 2596 passed / 6 skipped
LAST_UPDATED_BY: sysadmin (prgs-reviewer)

[THREAD STATE LEDGER]

  • lease: reviewer lease held (session 4979-b6d4f1861a70, comment 10407)
  • #332 terminal-review lock: clear (has_lock=false; prior cleanup comment 10403)
  • prior formal reviews at head: none
  • controller override for #671 recorded in comment 10366
  • decision: APPROVE — clean and merge-authorizing; do NOT merge without explicit operator authorization
## Review verdict: APPROVE PR #677 fully satisfies Issue #671 (block direct stable-branch pushes from MCP workflow sessions — #670 AC5 hardening). Reviewed at head `5933d87647656643a67a50331c4c7b06ea751dad` in an isolated worktree; all validation green against current `prgs/master` baseline (`bee24e2`). ### Scope 6 files, all additive (+1295 / -0). No unrelated changes. `git diff --check` clean. `stable_branch_push_guard.py` (new pure module), `gitea_mcp_server.py`, `mcp_session_state.py`, `skills/llm-project-workflow/SKILL.md`, and two test files. ### Acceptance criteria — all met - **AC1 detect direct stable-branch pushes:** `classify_push_command` covers `git push <remote> master`, refspecs (`HEAD:master`, `+refs/heads/x:refs/heads/master`), `--force`/`--force-with-lease`, delete (`--delete`, `:master`), `--dry-run`/`-n` no-op probes (dry-run still `proves_intent`), and compound commands. Stable set = `author_proofs.PROTECTED_BRANCHES` so the two guards cannot drift. - **AC2 root/control-checkout local commit:** `assess_root_checkout_local_commit`; `branches/` worktrees exempt; control-checkout HEAD advanced past `prgs/master` flagged; missing state reported `unknown` (positive-evidence only). - **AC3 contamination marker:** durable `KIND_STABLE_BRANCH_CONTAMINATION`, keyed per profile identity; command summary redacted via `redact_command` (URL userinfo, `TOKEN=`/`PASSWORD=`, bearer) — secrets never persist. - **AC4 fail-closed gate:** `_enforce_stable_branch_contamination_gate` wired into `verify_preflight_purity`; gated set covers review/merge/close/submit/complete; `comment_issue`/`lock_issue` stay allowed for audit handoff; reconciler exempt. - **AC5 tests:** 37 + 14 cover no-op dry-run, real direct push, sanctioned Gitea merge (non-push), fetch-only, root-checkout commit, feature-branch allowed, gate block, reconciler-exempt, redaction. - **AC6 docs:** SKILL.md universal rule + BLOCKED+DIAGNOSE blocker class + dedicated "Stable Branch Push Protection" section. ### Non-goals honored Sanctioned `gitea_merge_pr` / API merge not blocked (not a push); `git fetch` / `pull --ff-only` allowed; feature-branch pushes allowed — all asserted by tests. Reconciler-only clear enforced (`role != "reconciler"` → refused; worker cannot self-clear). ## Canonical PR State STATE: reviewed-approved WHO_IS_NEXT: merger NEXT_ACTION: Merger merges PR #677 via sanctioned Gitea path on explicit operator authorization, then closes #671. NEXT_PROMPT: ```text Role: MERGER. Repo prgs/Scaled-Tech-Consulting/Gitea-Tools. Merge PR #677 (Issue #671). Adopt reviewer lease (session 4979-b6d4f1861a70), re-pin live head 5933d87647656643a67a50331c4c7b06ea751dad, confirm open+mergeable+APPROVED at that head, merge via gitea_merge_pr ONLY on explicit operator authorization. No direct stable-branch push. Then close #671. ``` WHAT_HAPPENED: Reviewer approved PR #677 at head 5933d87 in isolated worktree branches/review-pr-677; full suite 2596 passed / 6 skipped; scope additive-only and matches #671. WHY: All six acceptance criteria (detection, root-checkout detection, contamination marker, fail-closed gate, tests, docs) are satisfied; redaction and reconciler-only clear verified; non-goals honored; no unrelated changes. ISSUE: 671 HEAD_SHA: 5933d87647656643a67a50331c4c7b06ea751dad REVIEW_STATUS: APPROVED MERGE_READY: yes (pending merger action + explicit operator authorization) BLOCKERS: none VALIDATION: git diff --check clean; test_stable_branch_push_guard.py 37 passed; test_stable_branch_contamination_server.py 14 passed; preflight/session/purity/contamination subset 60 passed; full suite 2596 passed / 6 skipped LAST_UPDATED_BY: sysadmin (prgs-reviewer) [THREAD STATE LEDGER] - lease: reviewer lease held (session 4979-b6d4f1861a70, comment 10407) - #332 terminal-review lock: clear (has_lock=false; prior cleanup comment 10403) - prior formal reviews at head: none - controller override for #671 recorded in comment 10366 - decision: APPROVE — clean and merge-authorizing; do NOT merge without explicit operator authorization
Owner

adopted_at: 2026-07-12T00:36:39Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 4979-b6d4f1861a70
adopted_from_profile: prgs-reviewer
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 10407
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #677
issue: #671
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 22311-a6609e26ac4e
worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-677
phase: adopted
candidate_head: 5933d87647
target_branch: master
target_branch_sha: bee24e2b10
last_activity: 2026-07-12T00:36:39Z
expires_at: 2026-07-12T02:36:39Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-12T00:36:39Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 4979-b6d4f1861a70 adopted_from_profile: prgs-reviewer adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 10407 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #677 issue: #671 reviewer_identity: sysadmin profile: prgs-merger session_id: 22311-a6609e26ac4e worktree: /Users/jasonwalker/Development/Gitea-Tools/branches/review-pr-677 phase: adopted candidate_head: 5933d87647656643a67a50331c4c7b06ea751dad target_branch: master target_branch_sha: bee24e2b102dbdc201932d2e5397a7d9e3be5fc5 last_activity: 2026-07-12T00:36:39Z expires_at: 2026-07-12T02:36:39Z blocker: none
Owner

adopted_at: 2026-07-12T01:05:20Z
adopted_by_identity: sysadmin
adopted_by_profile: prgs-merger
adopted_from_session_id: 22311-a6609e26ac4e
adopted_from_profile: prgs-merger
adopted_from_reviewer_identity: sysadmin
adopted_from_comment_id: 10413
adoption_reason: merger-handoff-approved-head

repo: Scaled-Tech-Consulting/Gitea-Tools
pr: #677
issue: #671
reviewer_identity: sysadmin
profile: prgs-merger
session_id: 46876-408426bdff6d
worktree: branches/review-pr-677
phase: adopted
candidate_head: 5933d87647
target_branch: master
target_branch_sha: bee24e2b10
last_activity: 2026-07-12T01:05:20Z
expires_at: 2026-07-12T03:05:20Z
blocker: none

<!-- mcp-review-lease-adoption:v1 --> adopted_at: 2026-07-12T01:05:20Z adopted_by_identity: sysadmin adopted_by_profile: prgs-merger adopted_from_session_id: 22311-a6609e26ac4e adopted_from_profile: prgs-merger adopted_from_reviewer_identity: sysadmin adopted_from_comment_id: 10413 adoption_reason: merger-handoff-approved-head <!-- mcp-review-lease:v1 --> repo: Scaled-Tech-Consulting/Gitea-Tools pr: #677 issue: #671 reviewer_identity: sysadmin profile: prgs-merger session_id: 46876-408426bdff6d worktree: branches/review-pr-677 phase: adopted candidate_head: 5933d87647656643a67a50331c4c7b06ea751dad target_branch: master target_branch_sha: bee24e2b102dbdc201932d2e5397a7d9e3be5fc5 last_activity: 2026-07-12T01:05:20Z expires_at: 2026-07-12T03:05:20Z blocker: none
sysadmin merged commit 22698c1b5f into master 2026-07-11 20:05:45 -05:00
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#677