Prevent MCP session state from dirtying Git worktrees and provide sanctioned legacy-state recovery #716

Open
opened 2026-07-15 19:10:57 -05:00 by jcwalker3 · 1 comment
Owner

Problem

Gitea-Tools previously allowed GITEA_MCP_SESSION_STATE_DIR to point inside a Git control checkout.

A confirmed example is:

/Users/jasonwalker/Development/Gitea-Tools/.mcp_session_701/

The directory contains historical prgs-reviewer workflow and review-decision state for PR #701/review 431. It remains untracked in the control checkout and causes the canonical author workflow’s clean-checkout preflight to fail.

The live daemon no longer considers this directory authoritative because Issue #695 moved the active session-state authority to:

~/.cache/gitea-tools/session-state

As a result, the directory is trapped between two safeguards:

  • The author workflow correctly refuses to proceed because the control checkout is dirty.
  • Native stale-lock cleanup checks only the current authoritative state directory and reports no live lock.
  • Manual deletion is forbidden because the legacy directory contains retained forensic evidence.
  • The contaminated reviewer cannot quarantine its own historical approval.
  • Closing or merging the historical PR would not clean the directory because native cleanup cannot address the legacy path.

Normal MCP operation has therefore created residue that makes Gitea-Tools fail its own workflow preflight, with no sanctioned recovery path.

Confirmed evidence

  • Control checkout: master
  • Control HEAD: 1eafb757a91e4606ed9f82413e4122ad6bcf94fd
  • Remaining untracked entry: .mcp_session_701/
  • Historical state owner: prgs-reviewer
  • Historical PR: fix: map Gitea auth failures to structured MCP tool errors (Closes #699) (#701)
  • Historical review: 431
  • Historical head: 6b675f5c834b41f9d74e8a54294ff44dddf28ae4
  • Review 431 is already quarantined under #695.
  • Its approval is not valid merge authorization.
  • PR #701 remains open and requires a fresh review.
  • Current native cleanup reports has_lock=false because it reads the new authoritative directory.
  • Current native cleanup reports cleanup_allowed=false.
  • The legacy directory was previously retained as forensic evidence.
  • No process currently holds a file open inside the directory; this fact alone is not treated as cleanup authorization.
  • PR #715 remediation is blocked because the canonical workflow requires a clean control checkout.

Required behavior

1. Safe session-state location

Gitea-Tools must never place runtime session state inside:

  • a Git control checkout;
  • a linked Git worktree;
  • a repository’s Git common directory;
  • the configured branches/ worktree container;
  • any path that resolves into those locations through symlinks or relative traversal.

Use an appropriate platform state directory outside repositories. Audit-relevant state must not rely on a disposable cache location without an explicit retention design.

2. Startup validation

At daemon startup:

  • canonicalize the configured state path;
  • resolve symlinks;
  • discover enclosing Git worktrees and common directories;
  • reject unsafe in-repository paths before writing any state;
  • return a structured, actionable configuration error;
  • do not silently fall back to another location;
  • do not create a partial session before validation completes.

A sanctioned test-only temporary directory may be supported, but it must not become a production bypass.

3. Secure defaults and permissions

Provide a secure platform-appropriate default state directory.

Requirements:

  • owner-only directory permissions;
  • restrictive file permissions;
  • atomic file replacement;
  • no secrets in error output;
  • no environment variable may weaken path validation;
  • concurrent MCP processes must not corrupt or overwrite each other’s state.

4. Legacy-state discovery

Provide a native, read-only inventory operation that can discover legacy session-state directories outside the current authority.

It must report:

  • canonical path;
  • profile owner;
  • session/run identity;
  • recorded PR, issue, review and head;
  • whether the state is active, obsolete, quarantined, superseded or unknown;
  • whether the current daemon reads it;
  • whether live processes reference it;
  • cleanup or quarantine eligibility;
  • required role and recovery operation.

Discovery must not treat a dead PID as sufficient proof of staleness.

5. Sanctioned migration and quarantine

Provide a role-gated recovery operation for legacy state.

It must:

  • require an authorized reconciler/controller identity;
  • preserve profile ownership and provenance;
  • preserve valid locks and audit evidence;
  • preserve quarantined-review evidence;
  • verify checksums before removing a source copy;
  • use atomic or crash-recoverable migration;
  • reject collisions with current authoritative state;
  • refuse overwriting newer state;
  • support dry-run assessment;
  • produce a durable audit record;
  • fail closed on uncertain provenance;
  • never authorize a second terminal review decision improperly.

6. Forensic residue retirement

Provide a sanctioned path for retained evidence that no longer needs to remain inside a repository.

The operation must:

  • move it to a protected external forensic/quarantine location;
  • retain identifiers, timestamps and checksums;
  • retain the reason it was quarantined;
  • verify the destination before removing the repository copy;
  • distinguish evidence relocation from decision-lock cleanup;
  • leave the Git control checkout genuinely clean.

Do not solve this by adding .mcp_session_* to .gitignore or local Git excludes. Hiding runtime residue would weaken the clean-checkout guard.

7. Clean-checkout integration

After sanctioned migration or quarantine:

  • the control checkout must pass the canonical clean-checkout preflight;
  • no tracked file or Git ref may change;
  • no unrelated untracked content may be removed;
  • the workflow must report the exact recovery audit record;
  • author and reviewer workflows must be able to continue without exceptions or bypass flags.

Acceptance criteria

  1. Startup rejects a session-state directory inside a normal checkout.
  2. Startup rejects a directory inside a linked worktree.
  3. Startup rejects a directory inside the Git common directory.
  4. Startup rejects relative and symlink paths that resolve into a repository.
  5. Startup accepts the secure external state directory.
  6. Missing, malformed or unsafe state configuration fails closed before state is written.
  7. A test-only boundary cannot be invoked by production callers.
  8. Legacy state can be inventoried by explicit path without making it authoritative.
  9. Active state cannot be moved, deleted or overwritten.
  10. Unknown-provenance state fails closed.
  11. Quarantined review evidence is preserved during relocation.
  12. Migration verifies hashes and uses crash-safe replacement.
  13. Concurrent daemon processes cannot corrupt migration or active state.
  14. A current authoritative lock cannot be replaced by legacy state.
  15. Recovery creates a durable audit record.
  16. .mcp_session_701/ can be relocated through the sanctioned process without validating review 431 or modifying PR #701.
  17. After sanctioned relocation, the Gitea-Tools control checkout is genuinely clean.
  18. No .gitignore or local-exclude workaround is used.
  19. Regression tests prove ordinary MCP operation never creates session artifacts inside a Git checkout.
  20. Documentation covers configuration, migration, quarantine, rollback and operator recovery.

Required tests

Include coverage for:

  • macOS and Linux default state paths;
  • nested repositories;
  • linked worktrees;
  • Git common directories;
  • relative traversal;
  • symlink traversal;
  • missing directories;
  • permission failures;
  • malformed state;
  • checksum mismatch;
  • interrupted migration;
  • destination collision;
  • active-process state;
  • dead-PID state without other proof;
  • multiple profiles;
  • concurrent daemons;
  • quarantined reviews;
  • dry-run versus apply;
  • audit readback;
  • clean-checkout verification.

Scope boundaries

This issue must not:

  • restore validity to review 431;
  • merge, close or otherwise modify PR #701;
  • alter PR #710;
  • modify eAgenda or PR #194;
  • bypass the canonical clean-checkout guard;
  • authorize manual session-state deletion;
  • conflate runtime cache cleanup with forensic-evidence retirement.

Related work

  • Issue #695: authoritative session-state hardening and approval quarantine
  • PR #701: historical contaminated review state
  • Issue #714 / PR #715: session-context immutability remediation currently blocked by the dirty control checkout
  • Issue #332: terminal review-decision lock enforcement

Security impact

This is both a workflow-availability and audit-integrity defect. Session state must not dirty the repository it protects, but recovery must also preserve decision locks, quarantined approvals and forensic provenance. The system needs a native lifecycle-aware relocation path rather than manual deletion or Git ignore rules.

## Problem Gitea-Tools previously allowed `GITEA_MCP_SESSION_STATE_DIR` to point inside a Git control checkout. A confirmed example is: `/Users/jasonwalker/Development/Gitea-Tools/.mcp_session_701/` The directory contains historical `prgs-reviewer` workflow and review-decision state for PR #701/review 431. It remains untracked in the control checkout and causes the canonical author workflow’s clean-checkout preflight to fail. The live daemon no longer considers this directory authoritative because Issue #695 moved the active session-state authority to: `~/.cache/gitea-tools/session-state` As a result, the directory is trapped between two safeguards: - The author workflow correctly refuses to proceed because the control checkout is dirty. - Native stale-lock cleanup checks only the current authoritative state directory and reports no live lock. - Manual deletion is forbidden because the legacy directory contains retained forensic evidence. - The contaminated reviewer cannot quarantine its own historical approval. - Closing or merging the historical PR would not clean the directory because native cleanup cannot address the legacy path. Normal MCP operation has therefore created residue that makes Gitea-Tools fail its own workflow preflight, with no sanctioned recovery path. ## Confirmed evidence - Control checkout: `master` - Control HEAD: `1eafb757a91e4606ed9f82413e4122ad6bcf94fd` - Remaining untracked entry: `.mcp_session_701/` - Historical state owner: `prgs-reviewer` - Historical PR: #701 - Historical review: 431 - Historical head: `6b675f5c834b41f9d74e8a54294ff44dddf28ae4` - Review 431 is already quarantined under #695. - Its approval is not valid merge authorization. - PR #701 remains open and requires a fresh review. - Current native cleanup reports `has_lock=false` because it reads the new authoritative directory. - Current native cleanup reports `cleanup_allowed=false`. - The legacy directory was previously retained as forensic evidence. - No process currently holds a file open inside the directory; this fact alone is not treated as cleanup authorization. - PR #715 remediation is blocked because the canonical workflow requires a clean control checkout. ## Required behavior ### 1. Safe session-state location Gitea-Tools must never place runtime session state inside: - a Git control checkout; - a linked Git worktree; - a repository’s Git common directory; - the configured `branches/` worktree container; - any path that resolves into those locations through symlinks or relative traversal. Use an appropriate platform state directory outside repositories. Audit-relevant state must not rely on a disposable cache location without an explicit retention design. ### 2. Startup validation At daemon startup: - canonicalize the configured state path; - resolve symlinks; - discover enclosing Git worktrees and common directories; - reject unsafe in-repository paths before writing any state; - return a structured, actionable configuration error; - do not silently fall back to another location; - do not create a partial session before validation completes. A sanctioned test-only temporary directory may be supported, but it must not become a production bypass. ### 3. Secure defaults and permissions Provide a secure platform-appropriate default state directory. Requirements: - owner-only directory permissions; - restrictive file permissions; - atomic file replacement; - no secrets in error output; - no environment variable may weaken path validation; - concurrent MCP processes must not corrupt or overwrite each other’s state. ### 4. Legacy-state discovery Provide a native, read-only inventory operation that can discover legacy session-state directories outside the current authority. It must report: - canonical path; - profile owner; - session/run identity; - recorded PR, issue, review and head; - whether the state is active, obsolete, quarantined, superseded or unknown; - whether the current daemon reads it; - whether live processes reference it; - cleanup or quarantine eligibility; - required role and recovery operation. Discovery must not treat a dead PID as sufficient proof of staleness. ### 5. Sanctioned migration and quarantine Provide a role-gated recovery operation for legacy state. It must: - require an authorized reconciler/controller identity; - preserve profile ownership and provenance; - preserve valid locks and audit evidence; - preserve quarantined-review evidence; - verify checksums before removing a source copy; - use atomic or crash-recoverable migration; - reject collisions with current authoritative state; - refuse overwriting newer state; - support dry-run assessment; - produce a durable audit record; - fail closed on uncertain provenance; - never authorize a second terminal review decision improperly. ### 6. Forensic residue retirement Provide a sanctioned path for retained evidence that no longer needs to remain inside a repository. The operation must: - move it to a protected external forensic/quarantine location; - retain identifiers, timestamps and checksums; - retain the reason it was quarantined; - verify the destination before removing the repository copy; - distinguish evidence relocation from decision-lock cleanup; - leave the Git control checkout genuinely clean. Do not solve this by adding `.mcp_session_*` to `.gitignore` or local Git excludes. Hiding runtime residue would weaken the clean-checkout guard. ### 7. Clean-checkout integration After sanctioned migration or quarantine: - the control checkout must pass the canonical clean-checkout preflight; - no tracked file or Git ref may change; - no unrelated untracked content may be removed; - the workflow must report the exact recovery audit record; - author and reviewer workflows must be able to continue without exceptions or bypass flags. ## Acceptance criteria 1. Startup rejects a session-state directory inside a normal checkout. 2. Startup rejects a directory inside a linked worktree. 3. Startup rejects a directory inside the Git common directory. 4. Startup rejects relative and symlink paths that resolve into a repository. 5. Startup accepts the secure external state directory. 6. Missing, malformed or unsafe state configuration fails closed before state is written. 7. A test-only boundary cannot be invoked by production callers. 8. Legacy state can be inventoried by explicit path without making it authoritative. 9. Active state cannot be moved, deleted or overwritten. 10. Unknown-provenance state fails closed. 11. Quarantined review evidence is preserved during relocation. 12. Migration verifies hashes and uses crash-safe replacement. 13. Concurrent daemon processes cannot corrupt migration or active state. 14. A current authoritative lock cannot be replaced by legacy state. 15. Recovery creates a durable audit record. 16. `.mcp_session_701/` can be relocated through the sanctioned process without validating review 431 or modifying PR #701. 17. After sanctioned relocation, the Gitea-Tools control checkout is genuinely clean. 18. No `.gitignore` or local-exclude workaround is used. 19. Regression tests prove ordinary MCP operation never creates session artifacts inside a Git checkout. 20. Documentation covers configuration, migration, quarantine, rollback and operator recovery. ## Required tests Include coverage for: - macOS and Linux default state paths; - nested repositories; - linked worktrees; - Git common directories; - relative traversal; - symlink traversal; - missing directories; - permission failures; - malformed state; - checksum mismatch; - interrupted migration; - destination collision; - active-process state; - dead-PID state without other proof; - multiple profiles; - concurrent daemons; - quarantined reviews; - dry-run versus apply; - audit readback; - clean-checkout verification. ## Scope boundaries This issue must not: - restore validity to review 431; - merge, close or otherwise modify PR #701; - alter PR #710; - modify eAgenda or PR #194; - bypass the canonical clean-checkout guard; - authorize manual session-state deletion; - conflate runtime cache cleanup with forensic-evidence retirement. ## Related work - Issue #695: authoritative session-state hardening and approval quarantine - PR #701: historical contaminated review state - Issue #714 / PR #715: session-context immutability remediation currently blocked by the dirty control checkout - Issue #332: terminal review-decision lock enforcement ## Security impact This is both a workflow-availability and audit-integrity defect. Session state must not dirty the repository it protects, but recovery must also preserve decision locks, quarantined approvals and forensic provenance. The system needs a native lifecycle-aware relocation path rather than manual deletion or Git ignore rules.
Author
Owner

One-time controller-authorized forensic relocation

Kind: controller-authorized forensic relocation of orphaned legacy session state (Issue #716 operational recovery; does not implement the durable product fix).

Paths

  • Source: /Users/jasonwalker/Development/Gitea-Tools/.mcp_session_701/
  • Destination: /Users/jasonwalker/Development/Gitea-Tools-Forensics/issue-716/pr701-session-state-20260715/
  • Manifest: /Users/jasonwalker/Development/Gitea-Tools-Forensics/issue-716/relocation-manifest-pr701-20260715.txt (mode 600)
  • Method: single atomic directory rename (mv) on the same filesystem

Related identifiers

Review 431 status

Review 431 remains quarantined. It provides no merge authorization. This relocation is not a validation of review 431 and does not re-authorize any historical approval.

Relocation results

  • Files relocated: 5 regular files (plus directory tree)
  • Hash verification: success — every post-move SHA-256, size, and relative path matched the pre-move inventory
  • Open handles on source before move: none
  • Source path after move: absent
  • Destination permissions: owner-only (700 dirs; files non-world-readable)

Authority and scope

  • Active session-state authority: ~/.cache/gitea-tools/session-state (unchanged; not merged with legacy state)
  • Source was not authoritative (confirmed before move)
  • Native cleanup reads the active authority only; legacy path was not treated as live lock authority
  • No PR mutation: PR #701 not reviewed, merged, closed, or otherwise modified
  • PR #710, eAgenda, PR #194: untouched
  • No .gitignore / exclude workaround
  • No tracked file or Git ref changes

Control-checkout before / after

  • Before: master @ 1eafb757a91e4606ed9f82413e4122ad6bcf94fd; sole untracked entry .mcp_session_701/
  • After: master @ 1eafb757a91e4606ed9f82413e4122ad6bcf94fd; genuinely clean (no tracked or untracked changes)

Residual

Issue #716 remains required for the permanent product fix (prevent in-repo session state and provide sanctioned legacy recovery in product code). This comment records only the one-time forensic relocation.

## One-time controller-authorized forensic relocation **Kind:** controller-authorized forensic relocation of orphaned legacy session state (Issue #716 operational recovery; does **not** implement the durable product fix). ### Paths - **Source:** `/Users/jasonwalker/Development/Gitea-Tools/.mcp_session_701/` - **Destination:** `/Users/jasonwalker/Development/Gitea-Tools-Forensics/issue-716/pr701-session-state-20260715/` - **Manifest:** `/Users/jasonwalker/Development/Gitea-Tools-Forensics/issue-716/relocation-manifest-pr701-20260715.txt` (mode `600`) - **Method:** single atomic directory rename (`mv`) on the same filesystem ### Related identifiers - **Issue:** #716 (permanent product fix remains required) - **Historical PR:** #701 - **Historical review:** 431 - **Recorded head:** `6b675f5c834b41f9d74e8a54294ff44dddf28ae4` - **Profile owner (legacy files):** `prgs-reviewer` - **Operator (this recovery):** `sysadmin` / controller-authorized path; audit posted via `prgs-author` (`jcwalker3`) ### Review 431 status Review **431 remains quarantined**. It provides **no merge authorization**. This relocation is **not** a validation of review 431 and does **not** re-authorize any historical approval. ### Relocation results - **Files relocated:** 5 regular files (plus directory tree) - **Hash verification:** success — every post-move SHA-256, size, and relative path matched the pre-move inventory - **Open handles on source before move:** none - **Source path after move:** absent - **Destination permissions:** owner-only (`700` dirs; files non-world-readable) ### Authority and scope - **Active session-state authority:** `~/.cache/gitea-tools/session-state` (unchanged; not merged with legacy state) - **Source was not authoritative** (confirmed before move) - **Native cleanup** reads the active authority only; legacy path was not treated as live lock authority - **No PR mutation:** PR #701 not reviewed, merged, closed, or otherwise modified - **PR #710, eAgenda, PR #194:** untouched - **No `.gitignore` / exclude workaround** - **No tracked file or Git ref changes** ### Control-checkout before / after - **Before:** `master` @ `1eafb757a91e4606ed9f82413e4122ad6bcf94fd`; sole untracked entry `.mcp_session_701/` - **After:** `master` @ `1eafb757a91e4606ed9f82413e4122ad6bcf94fd`; **genuinely clean** (no tracked or untracked changes) ### Residual Issue **#716 remains required** for the permanent product fix (prevent in-repo session state and provide sanctioned legacy recovery in product code). This comment records only the one-time forensic relocation.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

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