Skip to content

fix(guardrails): block merge bypass commands#191

Merged
terisuke merged 1 commit intodevfrom
codex/fix-merge-guardrail-bypasses
Apr 25, 2026
Merged

fix(guardrails): block merge bypass commands#191
terisuke merged 1 commit intodevfrom
codex/fix-merge-guardrail-bypasses

Conversation

@terisuke
Copy link
Copy Markdown

Summary

  • Treat GitHub REST API PR merges as guarded PR merges, including review and post-merge validation gates
  • Block reset-to-base soft/mixed syncs that bypass rebase/merge conflict guardrails
  • Require explicit worktree selection for codex exec reviews to avoid reviewing the wrong checkout
  • Add focused guardrail tests for the NFC-profile-card failure modes

Validation

  • bun test test/plugin/guardrail-git.test.ts test/plugin/guardrail-review.test.ts test/plugin/guardrail-secret-mask.test.ts
  • bun typecheck
  • git diff --check
  • bun run build
  • ~/.local/bin/opencode --version
  • packages/opencode/dist/opencode-darwin-arm64/bin/opencode --version

Copilot AI review requested due to automatic review settings April 25, 2026 16:27
@github-actions
Copy link
Copy Markdown

This PR doesn't fully meet our contributing guidelines and PR template.

What needs to be fixed:

  • PR description is missing required template sections. Please use the PR template.

Please edit this PR description to address the above within 2 hours, or it will be automatically closed.

If you believe this was flagged incorrectly, please let a maintainer know.

@github-actions
Copy link
Copy Markdown

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@terisuke terisuke force-pushed the codex/fix-merge-guardrail-bypasses branch from c333d10 to c36d867 Compare April 25, 2026 16:30
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR tightens the guardrail-git plugin so PR merges can’t bypass review/conflict gates via alternative merge commands, and adds regression tests to cover newly blocked flows.

Changes:

  • Treat gh api .../pulls/<n>/merge as a PR merge so merge/review gates run for GitHub REST API merges.
  • Block git reset --soft|--mixed resets to base branches to prevent bypassing rebase/merge conflict guardrails.
  • Require an explicit worktree flag for codex exec to avoid reviewing the wrong checkout; add focused tests for these guardrails.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
packages/opencode/test/plugin/guardrail-git.test.ts Adds unit tests for API-merge bypass, reset-to-base bypass, and codex exec worktree requirement.
packages/guardrails/profile/plugins/guardrail-git.ts Expands merge detection to include gh api merges, adds reset-to-base blocking, and enforces explicit worktree for codex exec.
Comments suppressed due to low confidence (1)

packages/guardrails/profile/plugins/guardrail-git.ts:313

  • The reset-to-base detection regex will also match refs like origin/dev-feature because dev is followed by - (a word boundary), so it can over-block resets that are not targeting the protected base branches. Tighten the ref matching so it only triggers when the target is exactly (origin/)?main|master|develop|dev as a whole token (e.g., require (?:\s|$) or end-of-ref delimiters after the branch name).
    if (/\bgit\s+rebase\b/i.test(cmd) && !/--abort\b/i.test(cmd)) {
      if (/\bgit\s+rebase\s+(origin\/)?(main|master|develop)\b/i.test(cmd)) {
        await ctx.mark({ rebase_session_active: true, rebase_session_at: new Date().toISOString() })

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +28 to +30
cmd.match(/\bgh\s+api\b(?=[\s\S]*(?:^|\s)-X(?:=|\s*)PUT\b)[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i)?.[1] ??
cmd.match(
/\bgh\s+api\b(?=[\s\S]*(?:^|\s)--method(?:=|\s+)PUT\b)[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i,
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ghPrMergeNumber only detects gh api merges when the method is spelled with a space (-X PUT / --method PUT). gh api (cobra/pflag) also supports --method=PUT and commonly -X=PUT / -XPUT, which would bypass the merge guard. Update the method detection regexes to accept the =PUT (and no-space short flag) forms so the bypass is reliably blocked.

Suggested change
cmd.match(/\bgh\s+api\b(?=[\s\S]*(?:^|\s)-X(?:=|\s*)PUT\b)[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i)?.[1] ??
cmd.match(
/\bgh\s+api\b(?=[\s\S]*(?:^|\s)--method(?:=|\s+)PUT\b)[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i,
cmd.match(/\bgh\s+api\b(?=[\s\S]*(?:^|\s)-X(?:PUT|=PUT|\s+PUT\b))[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i)?.[1] ??
cmd.match(
/\bgh\s+api\b(?=[\s\S]*(?:^|\s)--method(?:=PUT\b|\s+PUT\b))[\s\S]*\brepos\/\S+\/pulls\/(\d+)\/merge\b/i,

Copilot uses AI. Check for mistakes.
function codexExecWorktree(cmd: string) {
return (
cmd
.match(/\bcodex\s+exec\b[\s\S]*(?:\s-C|\s--cd|\s--cwd)\s+(?:"([^"]+)"|'([^']+)'|(\S+))/i)
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The codex exec worktree check only matches -C <path>, --cd <path>, --cwd <path> with a whitespace-separated value. If the CLI is invoked with --cwd=<path> / --cd=<path> (or quoted paths with spaces), this guard will incorrectly block even though an explicit worktree was provided. Consider broadening the regex to accept --cwd= / --cd= / -C= forms and quoted values.

Suggested change
.match(/\bcodex\s+exec\b[\s\S]*(?:\s-C|\s--cd|\s--cwd)\s+(?:"([^"]+)"|'([^']+)'|(\S+))/i)
.match(
/\bcodex\s+exec\b[\s\S]*(?:\s-C|\s--cd|\s--cwd)(?:\s*=\s*|\s+)(?:"([^"]+)"|'([^']+)'|(\S+))/i,
)

Copilot uses AI. Check for mistakes.
Comment on lines +47 to +49
}

function sameWorktree(left: string, right: string) {
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guard currently blocks any codex exec invocation without an explicit worktree, but the error message says "codex exec review must…". Either scope the check to review-related codex commands, or adjust the message so it matches the actual behavior (all codex exec commands).

Copilot uses AI. Check for mistakes.
Comment on lines +93 to +122
test("blocks GitHub API pull request merge bypasses", async () => {
await using fixture = await context()
const git = createGitHandlers(fixture.ctx, review())

await expect(
git.bashBeforeGit("gh api -X PUT repos/Cor-Incorporated/nfc-profile-card/pulls/42/merge", {}, {}),
).rejects.toThrow("merge blocked")

expect(fixture.marks.some((item) => String(item.last_reason).includes("GLM code-reviewer"))).toBe(true)
})

test("blocks GitHub API pull request merge bypasses with equals method flags", async () => {
await using fixture = await context()
const git = createGitHandlers(fixture.ctx, review())

await expect(
git.bashBeforeGit("gh api --method=PUT repos/Cor-Incorporated/nfc-profile-card/pulls/42/merge", {}, {}),
).rejects.toThrow("merge blocked")
})

test("blocks reset-to-base sync bypasses", async () => {
await using fixture = await context()
const git = createGitHandlers(fixture.ctx, review())

await expect(git.bashBeforeGit("git reset --soft origin/dev", {}, {})).rejects.toThrow("reset-to-base sync blocked")

expect(fixture.marks.at(-1)?.last_reason).toBe("branch reset sync blocked")
})

test("requires explicit worktree for codex exec reviews", async () => {
Copy link

Copilot AI Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new tests cover gh api -X PUT …/merge and codex exec -C …, but they don’t cover important accepted flag variants that could regress or bypass the guardrails (e.g. gh api --method=PUT …/merge, codex exec --cwd=/tmp/project …, and a non-base ref like git reset --soft origin/dev-feature). Adding these cases would help ensure the regexes don’t allow bypasses or cause false positives.

Copilot uses AI. Check for mistakes.
@terisuke terisuke merged commit 35baa6f into dev Apr 25, 2026
2 of 8 checks passed
@terisuke terisuke deleted the codex/fix-merge-guardrail-bypasses branch April 25, 2026 16:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants