# Tier 2 Sandbox File Leak Prevention — Plan **Track:** `tier2_leak_prevention_20260620` **Created:** 2026-06-20 **Status:** SHIPPED (4 atomic commits) This plan was authored retroactively after the work was completed in-session (in response to a user request: "tier-2 files leaked into master via commit 00e5a3f2; undo them and add a guard"). The plan is recorded here for traceability per `conductor/workflow.md` "Plan is the source of truth." ## Phases ### Phase 1: Revert the offender commit (selective) **Commit:** `fab2e55b fix(tier2): undo sandbox file leaks from 00e5a3f2` **WHERE:** `git revert -n 00e5a3f2` then surgically unstage files outside the user's scope. **WHAT:** - Delete `.opencode/agents/tier2-autonomous.md` - Delete `.opencode/commands/tier-2-auto-execute.md` - Revert `mcp_paths.toml` extra_dirs to `["C:/projects/gencpp"]` - Revert `opencode.json` MCP path to `manual_slop`, default_agent to `tier2-tech-lead` - Leave at HEAD: 4 throwaway scripts in `scripts/tier2/artifacts/.../*.py`, `project_history.toml` timestamp **HOW:** `git revert -n` (apply without committing), then `git reset HEAD -- ` to unstage the files outside scope, then `git checkout HEAD -- ` to restore them to HEAD's content. Resolve the modify/delete conflict on `tier2-autonomous.md` (commit `07f46bfd` modified it after the offender added it) by deletion. **SAFETY:** User's project-level config files (config.toml, project.toml, etc.) were uncommitted at session start; stashed them as `stash@{0}` (tier2-safety-checkpoint) before the revert to avoid losing them. Commit with explicit message + git note. ### Phase 2: Pre-commit hook + config + tests **Commit:** `81e1fd7b feat(tier2): add pre-commit hook + denylist config to block sandbox-only files` **WHERE:** - NEW `conductor/tier2/githooks/pre-commit` - NEW `conductor/tier2/githooks/forbidden-files.txt` - NEW `tests/test_tier2_pre_commit_hook.py` **WHAT:** A shell script that auto-unstages forbidden files from any tier-2 commit. Configurable via a separate denylist file (one substring pattern per line; `#` comments and blanks ignored). **HOW:** 1. Write 12 failing tests in `tests/test_tier2_pre_commit_hook.py` (TDD red phase) 2. Write `conductor/tier2/githooks/pre-commit` as a `#!/bin/sh` script 3. Write `conductor/tier2/githooks/forbidden-files.txt` with 4 specific patterns 4. Run tests; verify all 12 pass (green phase) **SAFETY:** - Hook always exits 0 (removes the leak rather than blocking the commit; tier-2 cannot run `git restore --staged` per sandbox rules) - Uses `git rm --cached --force` (NOT `git restore`; required when staged content diverges from HEAD and working tree; discovered during TDD) - Hook source file is plain POSIX sh; no Python dependency; works under Git Bash on Windows - 12 tests cover: empty staged set, allowed files, each forbidden file type, multi-file unstaging, mixed staged sets, hook silence, hook warning, config-driven denylist, paths with spaces ### Phase 3: Audit script + tests **Commit:** `f5d8ea04 feat(audit): add audit_tier2_leaks.py for tier-2 sandbox file leak detection` **WHERE:** - NEW `scripts/audit_tier2_leaks.py` - NEW `tests/test_audit_tier2_leaks.py` **WHAT:** A Python script that scans the main repo's working tree for files matching the forbidden patterns. Reports any matches as leaks. Default mode is informational (exit 0); `--strict` mode exits 1 on leaks (CI gate). **HOW:** 1. Write 13 failing tests (TDD red phase) 2. Implement `scripts/audit_tier2_leaks.py` with argparse (--strict, --json flags) 3. Run tests; verify all 13 pass **SAFETY:** - Only reports `untracked` and `modified` files (tracked-and-clean files in the main repo are legitimate; patterns are about CONTENT not file existence) - Skips `tests/`, `conductor/`, `node_modules/`, `.git/`, etc. - Missing config file: warn to stderr, exit 0 (graceful degradation; hook also no-ops) - Script uses `git ls-files` and `git diff --name-only` via subprocess; no shell injection risk ### Phase 4: Wire the hook into setup_tier2_clone.ps1 **Commit:** `8f54deda chore(tier2): install pre-commit hook via setup_tier2_clone.ps1` **WHERE:** `scripts/tier2/setup_tier2_clone.ps1` step 4 (Install git hooks) **WHAT:** Add `Copy-Item` for the new `pre-commit` hook alongside the existing `pre-push` and `post-checkout` hooks. Existing tier-2 clones need to re-run setup to install the new hook; new clones get it automatically. **HOW:** Single-line addition to the existing git hooks installation block. The forbidden-files.txt config is already committed to the clone by the canonical-source commit, so the hook can find it via the project root. **SAFETY:** The copy is idempotent (uses `-Force`). Tested by `tests/test_tier2_setup_bootstrap.py` (3 opt-in tests; all pass with the change). ## Verification | Test file | Default-on tests | Opt-in tests | |-----------|------------------|--------------| | `tests/test_audit_tier2_leaks.py` | 13 | 0 | | `tests/test_tier2_pre_commit_hook.py` | 12 | 0 | | `tests/test_tier2_setup_bootstrap.py` | 0 | 3 | | `tests/test_tier2_sandbox_enforcement.py` | 0 | 1 | | `tests/test_tier2_slash_command_spec.py` | 17 | 0 | **Total: 42 default-on + 4 opt-in** (all pass when the right env vars are set). Manual end-to-end verification: created a fake git repo, staged `opencode.json` with a sandbox-style modification, ran the hook, verified the file was unstaged and the commit proceeded without it. ## Atomic per-task commits Per `conductor/workflow.md` "ATOMIC PER-TASK COMMITS": 1. `fab2e55b fix(tier2): undo sandbox file leaks from 00e5a3f2` (Phase 1) 2. `81e1fd7b feat(tier2): add pre-commit hook + denylist config to block sandbox-only files` (Phase 2) 3. `f5d8ea04 feat(audit): add audit_tier2_leaks.py for tier-2 sandbox file leak detection` (Phase 3) 4. `8f54deda chore(tier2): install pre-commit hook via setup_tier2_clone.ps1` (Phase 4) Each commit has a `git notes add -m "..." ` summary explaining the why (per the workflow).