Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

fix: prevent firstmate pr targeting upstream - #31

Merged
JTInventory merged 6 commits into
mainfrom
fm/firstmate-pin-pr-target-fork-0701
Jul 1, 2026
Merged

JTInventory merged 6 commits into
mainfrom
fm/firstmate-pin-pr-target-fork-0701

Conversation

@JTInventory

Copy link
Copy Markdown
Owner

Intent

Ship a narrow Firstmate-owned guard so no-mistakes and PR creation for this repository cannot accidentally open or update PRs against kunchenguid/firstmate. The intended target is the captain fork JTInventory/firstmate. The change should use the concrete evidence that intended PR #30 was on JTInventory/firstmate and duplicate upstream PR kunchenguid#171 was opened on kunchenguid/firstmate, keep behavior narrow, avoid unrelated no-mistakes CI monitor, durable-profile, behavior-test runtime, watcher, secondmate routing, or fm-send changes, add focused fork-parent target tests, document only the captain fork target, and stop before PR creation if any path would target kunchenguid/firstmate.

What Changed

  • Added a Firstmate no-mistakes PR target guard that allows only the captain fork JTInventory/firstmate and fails closed for upstream kunchenguid/firstmate, targetless local gates, unsafe no-mistakes remotes, and override attempts.
  • Wired the guard into no-mistakes configuration and documented when to run it before Firstmate no-mistakes push or PR creation paths.
  • Added focused shell coverage for safe captain-fork targets and blocked upstream, parent-gate, push URL, and override cases.

Risk Assessment

✅ Low: Captain, the change is narrow, fail-closed, and covered by focused behavior tests for the target-bypass cases reviewed here.

Testing

The prompt-provided baseline had already passed; I then exercised the focused fork-parent guard suite, captured manual operator-facing accept/block transcripts, verified the live worktree target, and reran the full configured command with the new guard prefix. All required checks passed, with intentional upstream-target cases exiting nonzero and printing the expected block messages.

Evidence: manual PR target guard transcript
origin	https://github.com/JTInventory/firstmate (fetch)
origin	https://github.com/JTInventory/firstmate (push)

## safe-captain-fork
$ bash -lc cd '/tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/manual-cases/safe-repo' && '/root/.no-mistakes/worktrees/76db9585a6d5/01KWEVTCZ3EBK8BCQV84XWNHHK/bin/fm-no-mistakes-pr-target-guard.sh'
exit=0
stdout:
ok: PR target repo jtinventory/firstmate verified
stderr:

## blocks-upstream-origin
$ bash -lc cd '/tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/manual-cases/upstream-origin' && '/root/.no-mistakes/worktrees/76db9585a6d5/01KWEVTCZ3EBK8BCQV84XWNHHK/bin/fm-no-mistakes-pr-target-guard.sh'
exit=1
stdout:
stderr:
blocked: would target upstream remote.origin.url=https://github.com/kunchenguid/firstmate (normalized kunchenguid/firstmate), expected jtinventory/firstmate

## blocks-upstream-gate
$ bash -lc cd '/tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/manual-cases/upstream-gate' && '/root/.no-mistakes/worktrees/76db9585a6d5/01KWEVTCZ3EBK8BCQV84XWNHHK/bin/fm-no-mistakes-pr-target-guard.sh'
exit=1
stdout:
stderr:
blocked: would target upstream no-mistakes gate remote.origin.url=https://github.com/kunchenguid/firstmate (normalized kunchenguid/firstmate), expected jtinventory/firstmate

## blocks-parent-override
$ bash -lc cd '/tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/manual-cases/parent-override' && FM_FIRSTMATE_PR_TARGET_REPO=kunchenguid/firstmate '/root/.no-mistakes/worktrees/76db9585a6d5/01KWEVTCZ3EBK8BCQV84XWNHHK/bin/fm-no-mistakes-pr-target-guard.sh'
exit=1
stdout:
stderr:
blocked: unsupported expected PR target kunchenguid/firstmate, only jtinventory/firstmate is allowed

## live-worktree
$ bash bin/fm-no-mistakes-pr-target-guard.sh
ok: PR target repo jtinventory/firstmate verified
exit=0
Evidence: focused PR target guard test output
ok - PR target guard accepts captain fork origin and no-mistakes gate
ok - PR target guard rejects parent origin target
ok - PR target guard rejects parent second origin target
ok - PR target guard rejects parent origin push URL target
ok - PR target guard rejects parent second origin push URL target
ok - PR target guard rejects parent no-mistakes gate target
ok - PR target guard rejects parent second no-mistakes gate target
ok - PR target guard rejects local no-mistakes gate without target
ok - PR target guard accepts captain fork nonlocal no-mistakes remote
ok - PR target guard rejects parent nonlocal no-mistakes remote
ok - PR target guard rejects parent nonlocal no-mistakes push URL
ok - PR target guard rejects parent expected target override
ok - PR target guard rejects parent expected target argument
ok - PR target guard rejects parent no-mistakes status target
Evidence: live worktree guard stdout

ok: PR target repo jtinventory/firstmate verified

ok: PR target repo jtinventory/firstmate verified

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed (4) ✅
  • ⚠️ bin/fm-no-mistakes-pr-target-guard.sh:64 - The guard only inspects the first origin push URL. Git supports multiple push URLs, and git remote get-url --push origin returns one unless --all is used, so a repo with an allowed first push URL plus a second https://github.com/kunchenguid/firstmate push URL would pass while git push origin can still target the upstream repo. Iterate over all origin fetch/push URLs, and use --get-all for gate config checks too.

🔧 Fix: Check every PR target URL
1 warning still open:

  • ⚠️ bin/fm-no-mistakes-pr-target-guard.sh:78 - The guard silently skips a no-mistakes remote when that remote URL is not a local directory. If origin is correctly set to JTInventory/firstmate but git push no-mistakes points directly at https://github.com/kunchenguid/firstmate, this check continues instead of blocking, so the new fail-closed guard still has a PR-target bypass. Treat a non-local no-mistakes URL as a target URL to verify, or fail closed when it cannot be inspected as a local gate.

🔧 Fix: Captain, verify no-mistakes remote targets
1 warning still open:

  • ⚠️ bin/fm-no-mistakes-pr-target-guard.sh:78 - When the no-mistakes remote points at an existing directory, the guard treats it as inspected even if git --git-dir="$gate" config fails or returns no remote.origin.url / remote.origin.pushurl. Because those reads are swallowed with || true, an uninspectable or targetless local gate can still pass as ok; fail closed unless the local gate yields at least one verifiable target URL.

🔧 Fix: Fail closed on targetless local gates
2 warnings still open:

  • ⚠️ bin/fm-no-mistakes-pr-target-guard.sh:83 - The guard checks remote.no-mistakes.url but never checks remote.no-mistakes.pushurl. Git uses pushurl for git push no-mistakes, so a repo with a safe no-mistakes fetch URL plus remote.no-mistakes.pushurl=https://github.com/kunchenguid/firstmate would pass this guard and still push through the upstream target. Inspect every git remote get-url --push --all no-mistakes URL too.
  • ⚠️ bin/fm-no-mistakes-pr-target-guard.sh:12 - The expected target can be changed with either an argument or FM_FIRSTMATE_PR_TARGET_REPO, which creates an allowlist escape hatch for the exact upstream repo this guard is meant to block. If the captain-owned target must remain only JTInventory/firstmate, remove the override or fail closed when the expected repo normalizes to kunchenguid/firstmate.

🔧 Fix: Captain, pin PR target guard
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
  • Baseline already provided by the prompt: command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
  • bash tests/fm-no-mistakes-pr-target-guard.test.sh > /tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/focused-pr-target-guard-test.log 2>&1
  • bash bin/fm-no-mistakes-pr-target-guard.sh > /tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/live-worktree-guard.out 2> /tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/live-worktree-guard.err
  • Manual CLI evidence cases recorded in /tmp/no-mistakes-evidence/01KWEVTCZ3EBK8BCQV84XWNHHK/manual-pr-target-guard-transcript.txt: safe captain fork, blocked upstream origin, blocked upstream no-mistakes gate, blocked parent override, live worktree guard.
  • command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; bash bin/fm-no-mistakes-pr-target-guard.sh || exit 1; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@JTInventory
JTInventory merged commit 34dcafd into main Jul 1, 2026
4 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant