Skip to content

fix(auto-merge): harden trusted actor enforcement - #670

Merged
devantler merged 14 commits into
mainfrom
codex/fix-auto-merge-vulnerability-in-workflow
Jul 28, 2026
Merged

fix(auto-merge): harden trusted actor enforcement#670
devantler merged 14 commits into
mainfrom
codex/fix-auto-merge-vulnerability-in-workflow

Conversation

@devantler

@devantler devantler commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

🤖 Generated by the Agentic Engineer.

Why

An allowlisted automation PR can receive a later pull-request event from a different actor. When actor-trust enforcement is enabled, the privileged auto-merge path must reject every untrusted head, lifecycle trigger, or rerun initiator, revoke merge state left by an earlier trusted event, and preserve event order even when GitHub schedules jobs out of order.

What

  • Add default-off enforce-actor-trust input for reusable callers and ENFORCE_ACTOR_TRUST variable support for direct/required-workflow runs.
  • When enabled, require every pull_request trigger on an allowlisted bot-authored PR to have both an allowlisted original github.actor and current github.triggering_actor; an untrusted collaborator cannot replay a trusted event.
  • Route rejected triggers to a separate GITHUB_TOKEN disarm job with the exact write scopes needed to revoke both classic auto-merge and merge-queue entries; it never receives the App private key.
  • Serialize accepted and rejected pull-request events in a newest-event-wins concurrency group: a newer rejection cancels older arming, repeated rejections coalesce safely, and a later trusted event is explicit reauthorization.
  • Bind accepted and rejected paths to both the event head and event updated_at. If live state changed, a stale rejection stands down only when the newest later run of the same caller workflow and PR was created strictly after the observed live update and has both original and rerun-triggering actors allowlisted. Same-second timestamps, unrelated workflows, earlier trusted runs, comments, reviews, and later untrusted events still revoke fail-closed.
  • Make live-state, workflow-identity, run-list, selector, and malformed-data failures call the real revocation script; only positively proven reauthorization skips.
  • Run all disarm helpers from the trusted PR base/called-workflow commit and pin checkout to v7.0.1.
  • Add enabled/default-off workflow invocations, trusted/rejected lifecycle and rerun fixtures, same/other-workflow and live-update ordering fixtures, independent classic/queue/no-op behavior tests, lookup-failure wrapper tests, pentad regression coverage, and consumer documentation.

Rollout

Legacy trusted-author behavior remains unchanged by default. Consumers opt in only after granting the reusable caller actions: read, contents: write, and pull-requests: write; missing ordering proof or revoke authority fails closed by revoking and/or leaving the required workflow red.

Fixes

Closes the actor-trust, lifecycle re-arm, untrusted-rerun, stale-head, stale-lifecycle, scheduling-inversion, and disarm-ordering gaps; no separate issue is linked.

@github-code-quality

github-code-quality Bot commented Jul 20, 2026

Copy link
Copy Markdown

Code Coverage Overview

Languages: Go, C#

Go / code-coverage/test

The overall coverage remains at 50%, unchanged from the main branch.

C# / code-coverage/dotnet

The overall coverage remains at 100%, unchanged from the main branch.


Updated July 20, 2026 09:41 UTC
Code Coverage is in Public Preview. Learn more and provide us with your feedback.

@github-actions

github-actions Bot commented Jul 20, 2026

Copy link
Copy Markdown

MegaLinter analysis: Success

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ COPYPASTE jscpd yes no no 0.35s
✅ GO revive 2 0 0 10.24s
✅ REPOSITORY betterleaks yes no no 0.94s
✅ REPOSITORY checkov yes no no 18.38s
✅ REPOSITORY gitleaks yes no no 0.12s
✅ REPOSITORY git_diff yes no no 0.06s
✅ REPOSITORY osv-scanner yes no no 0.63s
✅ REPOSITORY secretlint yes no no 0.86s
✅ REPOSITORY syft yes no no 2.76s
✅ REPOSITORY trivy yes no no 13.09s
✅ REPOSITORY trivy-sbom yes no no 0.19s
✅ REPOSITORY trufflehog yes no no 5.44s

Notices

📣 MegaLinter 9.5.0 is out! Discover the new features and security recommendations in the release announcement. (Skip this info by defining SECURITY_SUGGESTIONS: false)

See detailed reports in MegaLinter artifacts

Your project could benefit from a custom flavor, which would allow you to run only the linters you need, and thus improve runtime performances. (Skip this info by defining FLAVOR_SUGGESTIONS: false)

  • Documentation: Custom Flavors
  • Command: npx mega-linter-runner@9.6.0 --custom-flavor-setup --custom-flavor-linters COPYPASTE_JSCPD,GO_REVIVE,REPOSITORY_CHECKOV,REPOSITORY_GIT_DIFF,REPOSITORY_GITLEAKS,REPOSITORY_BETTERLEAKS,REPOSITORY_OSV_SCANNER,REPOSITORY_SECRETLINT,REPOSITORY_SYFT,REPOSITORY_TRIVY,REPOSITORY_TRIVY_SBOM,REPOSITORY_TRUFFLEHOG

MegaLinter is graciously provided by OX Security
Show us your support by starring ⭐ the repository

@devantler

Copy link
Copy Markdown
Contributor Author

Requested by the 🤖 Daily AI Engineer — CI is green at the current head and this draft carries no qualifying review, so requesting the lane-priority reviewer. Hygiene only: this is a sibling lane's draft and its owner promotes it.

@devantler

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 15abdec. Configure here.

Comment thread .github/workflows/enable-auto-merge.yaml Outdated
@devantler devantler changed the title fix(auto-merge): require trusted pull request actor fix(auto-merge): require trusted actor for head updates Jul 28, 2026
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

Refreshed the existing draft onto current main at cbafde3 and addressed the unresolved Bugbot finding. The actor allowlist now applies only to synchronize events; reopened and ready-for-review events remain eligible for trusted bot PRs when a maintainer triggers them.

Local proof at cbafde3:

  • RED: the classifier-shape test rejected the old unconditional actor gate.
  • GREEN: author-gate and pentad-gate fixture suites pass.
  • shellcheck, YAML parse, JSON parse, and git diff --check pass.
  • actionlint reports only the same pre-existing job.workflow_repository and job.workflow_sha context warnings reproduced from origin/main.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

@devantler: Reviewing the changes in #670 at cbafde3.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@devantler, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 23656b94-1040-4bf1-af20-2d10d3eaa9b9

📥 Commits

Reviewing files that changed from the base of the PR and between 61cdc93 and af9ce77.

📒 Files selected for processing (11)
  • .github/tests/disarm-auto-merge-mock-bin/gh
  • .github/tests/enable-auto-merge-authors.json
  • .github/tests/superseding-pr-runs.json
  • .github/tests/test-disarm-untrusted-event.sh
  • .github/tests/test-enable-auto-merge-author-gate.sh
  • .github/tests/test-find-trusted-superseding-pr-run.sh
  • .github/workflows/ci.yaml
  • .github/workflows/enable-auto-merge.yaml
  • .scripts/disarm-untrusted-event.sh
  • .scripts/find-trusted-superseding-pr-run.sh
  • README.md
📝 Walkthrough

Walkthrough

The auto-merge workflow adds opt-in actor-trust enforcement, actor-aware eligibility classification, and a disarm job that revokes rejected auto-merge or merge-queue state. Freshness and concurrency checks prevent stale events from arming merges. Fixtures, shell tests, CI aggregation, and README documentation cover the new behavior.

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly matches the PR’s main change: hardening trusted actor enforcement for auto-merge.
Description check ✅ Passed The description is detailed and directly describes the actor-trust, disarm, and ordering changes in the patch.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/tests/test-enable-auto-merge-author-gate.sh:
- Around line 72-78: Update the test setup around allowlist_json to define the
trusted PR-author list explicitly, rather than deriving it from the .eligible
fixture entries. Keep the existing reviewers_json pattern and ensure
expected_condition continues using the explicit trusted-author JSON for all
author checks.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6db6ba78-3837-44ce-b003-353a25410494

📥 Commits

Reviewing files that changed from the base of the PR and between 2cc7273 and cbafde3.

📒 Files selected for processing (3)
  • .github/tests/enable-auto-merge-authors.json
  • .github/tests/test-enable-auto-merge-author-gate.sh
  • .github/workflows/enable-auto-merge.yaml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (2)
.github/workflows/*.yaml

📄 CodeRabbit inference engine (AGENTS.md)

.github/workflows/*.yaml: Keep all GitHub Actions workflows under .github/workflows/.
Reusable workflows must use the workflow_call trigger.
Pin every remote action reference to a full commit SHA with a version comment; do not use remote self-references.
Include step-security/harden-runner as the first step of every reusable-workflow job, with egress-policy: audit.
Set top-level workflow permissions to {} and grant permissions per job.
Set persist-credentials: false on actions/checkout unless the job must push.
Workflows used as organization-level rulesets must include pull_request and merge_group triggers in addition to workflow_call.
For reusable workflows referencing a sibling action, check out the workflow repository at ${{ job.workflow_sha }} into .devantler-tech-actions, then invoke the action locally; remove the checkout before workspace-wide scans or commits.
New reusable-workflow jobs, steps, or behaviors must be behind a default-off boolean opt-in input and guarded with if: ${{ inputs.<enable-x> }}.
When a workflow supports both workflow_dispatch and workflow_call, normalize boolean inputs with inputs.<enable-x> == true || inputs.<enable-x> == 'true'.
Test both enabled and disabled states of every feature flag with CI test jobs.
Gating reusable workflows must have both a passing self-test and a failing-input self-test that verifies the expected finding; non-gating workflows require happy-path coverage.
Preserve tested consumer contracts, such as validate-go-project.yaml honoring .govulncheck-allow.txt; update the corresponding self-tests whenever the implementation changes.

Files:

  • .github/workflows/enable-auto-merge.yaml
.github/tests/**

📄 CodeRabbit inference engine (AGENTS.md)

Place deliberately bad fixtures for gating-workflow tests outside the gate's normal scan scope.

Files:

  • .github/tests/test-enable-auto-merge-author-gate.sh
  • .github/tests/enable-auto-merge-authors.json
🔇 Additional comments (3)
.github/workflows/enable-auto-merge.yaml (1)

65-82: LGTM!

.github/tests/enable-auto-merge-authors.json (1)

7-109: LGTM!

.github/tests/test-enable-auto-merge-author-gate.sh (1)

89-99: LGTM!

Comment thread .github/tests/test-enable-auto-merge-author-gate.sh Outdated
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

Addressed the current-head CodeRabbit finding at 9586391: the test now pins the trusted-author policy explicitly instead of deriving it from expected fixture outcomes. Relevant gate suites and local structure checks remain green; CI is rerunning on this head.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

@devantler: Reviewing the latest changes at 9586391.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review for auto-merge actor trust and regression coverage

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 958639185b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/enable-auto-merge.yaml Outdated
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

Addressed the current-head Codex finding at c6cb321. The rejected synchronize path now disarms stale classic auto-merge and merge-queue state in a separate GITHUB_TOKEN-only job; APP_PRIVATE_KEY is structurally excluded. RED/GREEN author-gate coverage, pentad-gate fixtures, shellcheck, YAML/JSON parsing, and diff hygiene pass locally. CI is rerunning on the new head.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='1791e38acca872f2a34487dc21a84fb9f94218c0'

echo '== checked-out HEAD =='
git rev-parse HEAD
echo '== requested commit =='
git rev-parse --verify "${target}^{commit}"
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== delta from first parent =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=100 "${target}^" "$target"

echo '== relevant paths =='
fd -HI -t f . | grep -E '(^|/)(disarm-auto-merge\.sh|test-disarm-auto-merge\.sh|enable-auto-merge\.yaml|ci\.yaml|test-enable-auto-merge-author-gate\.sh|enable-auto-merge-authors\.json)$' || true

echo '== relevant source and test files =='
for f in \
  .scripts/disarm-auto-merge.sh \
  .github/tests/test-disarm-auto-merge.sh \
  .github/tests/disarm-auto-merge-mock-bin/gh \
  .github/workflows/enable-auto-merge.yaml \
  .github/workflows/ci.yaml
do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

echo '== references to the disarm script/tests =='
rg -n -C 4 --hidden --glob '!.git/**' \
  '(disarm-auto-merge|mergeAutoMerge|enqueuePullRequest|disablePullRequestAutoMerge)' \
  .github .scripts README.md 2>/dev/null || true

Length of output: 212690


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '== author-gate permission and disarm assertions =='
sed -n '120,205p' .github/tests/test-enable-auto-merge-author-gate.sh

echo '== all disarm-job permission references =='
rg -n -C 5 --hidden --glob '!.git/**' \
  '(disarm-untrusted-update|contents:[[:space:]]*(read|write)|pull-requests:[[:space:]]*write)' \
  .github/tests .github/workflows/enable-auto-merge.yaml

echo '== parent revision of the disarm permissions =='
git show --no-ext-diff '1791e38^:.github/workflows/enable-auto-merge.yaml' | \
  sed -n '154,195p'

Length of output: 25496


@devantler Fresh substantive review completed for exact head 1791e38acca872f2a34487dc21a84fb9f94218c0.

No new findings. The split fixtures correctly isolate classic auto-merge disarming, merge-queue dequeueing, and the read-only no-op path; each also rejects the other state’s side effect. The surrounding structural checks continue to enforce the token-only disarm boundary, trusted script checkout, and newest-event-wins concurrency behavior.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1791e38acc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/enable-auto-merge.yaml Outdated
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

Fresh substantive review requested for exact head 61cdc93efdab64186b5f63f981ebb1e440a3427e; please focus on the stale-rejection ordering proof and fail-closed fallback.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review

Fresh substantive review requested for exact head 61cdc93efdab64186b5f63f981ebb1e440a3427e; the stale rejected-event finding is fixed and resolved.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='61cdc93efdab64186b5f63f981ebb1e440a3427e'

echo '== requested and checked-out commits =='
git rev-parse --verify "${target}^{commit}"
git rev-parse HEAD
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== delta from first parent =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=100 "${target}^" "$target"

echo '== relevant files =='
fd -HI -t f . | grep -E '(^|/)(enable-auto-merge\.yaml|disarm-auto-merge\.sh|test-disarm-auto-merge\.sh|test-enable-auto-merge-author-gate\.sh|enable-auto-merge-authors\.json|ci\.yaml)$' || true

echo '== workflow: concurrency, eligibility, and disarm sections =='
if [ -f .github/workflows/enable-auto-merge.yaml ]; then
  nl -ba .github/workflows/enable-auto-merge.yaml | sed -n '1,360p'
fi

echo '== disarm script and dedicated test =='
for f in .scripts/disarm-auto-merge.sh .github/tests/test-disarm-auto-merge.sh .github/tests/test-enable-auto-merge-author-gate.sh; do
  if [ -f "$f" ]; then
    echo "--- $f ---"
    cat -n "$f"
  fi
done

echo '== stale-ordering and fallback references =='
rg -n -C 8 --hidden --glob '!.git/**' \
  '(stale|fallback|concurren|cancel-in-progress|synchronize|disarm|updatedAt|head\.sha|workflow_run|event.*order)' \
  .github .scripts README.md 2>/dev/null || true

echo '== CI invocation context =='
if [ -f .github/workflows/ci.yaml ]; then
  rg -n -C 8 '(test-disarm-auto-merge|test-enable-auto-merge-author-gate)' .github/workflows/ci.yaml || true
fi

Length of output: 57950


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='61cdc93efdab64186b5f63f981ebb1e440a3427e'

echo '== complete disarm job through auto-merge resolution =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" | \
  awk '/^  disarm-untrusted-update:/{p=1} /^  auto-merge:/{if (p) q=1} {if (p) print} q && /^      - name: 🛂 Verify review and pre-merge gates/{exit}'

echo '== current dedicated disarm regression test =='
git show "${target}:.github/tests/test-disarm-auto-merge.sh"

echo '== current gh mock(s) used by disarm test =='
git ls-tree -r --name-only "$target" | grep -E 'disarm-auto-merge.*(mock|bin)|mock.*disarm-auto-merge' || true
while IFS= read -r path; do
  echo "--- $path ---"
  git show "${target}:${path}"
done < <(git ls-tree -r --name-only "$target" | grep -E 'disarm-auto-merge.*(mock|bin)|mock.*disarm-auto-merge' || true)

echo '== parent/current test delta for stale-order behavior =='
git diff --no-ext-diff --unified=120 "${target}^" "$target" -- \
  .github/tests/test-disarm-auto-merge.sh \
  .github/tests/test-enable-auto-merge-author-gate.sh

echo '== all tests and workflow references to ordering-proof inputs =='
git grep -n -E 'LATEST_ACTOR|RUN_ID|EVENT_UPDATED_AT|actions/runs|headRefOid|updatedAt|stale|supersed' "$target" -- \
  .github .scripts README.md || true

echo '== GitHub workflow-run API response handling in repository =='
git grep -n -E 'actions/runs|workflow_runs|run_attempt|workflow_id' "$target" -- \
  .github .scripts || true

Length of output: 17393


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='61cdc93efdab64186b5f63f981ebb1e440a3427e'

echo '== gh mock used by disarm-script fixtures =='
git show "${target}:.github/tests/disarm-auto-merge-mock-bin/gh"

echo '== author-gate assertions after the ordering section =='
git show "${target}:.github/tests/test-enable-auto-merge-author-gate.sh" | \
  awk '/disarm_step_env=/{p=1} p {print} /^exit "\$status"/{exit}'

echo '== all test references to the run-history endpoint and workflow identity =='
git grep -n -E '(actions/runs|workflow_runs|workflow_id|path|LATEST_ACTOR|supersed)' "$target" -- .github/tests || true

echo '== workflow identity fields available in the queried response are not used =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" | \
  awk '/run_pages=\$\(gh api/{p=1} p {print} /fi$/{if (++n == 2) exit}'

Length of output: 12027


@devantler Found one issue in the stale-rejection proof:

disarm-untrusted-update treats the newest repository-wide pull_request workflow run for the PR as a trusted reauthorization. The actions/runs query filters by event, timestamp, and PR number, but not by the Enable Auto-Merge workflow’s identity. Consequently, a trusted maintainer can trigger an unrelated PR workflow—for example, one listening for a label or assignment action—which updates the PR timestamp and yields a newer trusted run. The stale rejected synchronize event then exits without revoking auto-merge, even though this workflow never ran a trusted arming/reauthorization path for that later event.

Please restrict the candidate runs to this workflow (a stable workflow ID/path/name from the queried run metadata), and add fixtures for both a later trusted run of this workflow (skip is valid) and a later trusted run of another workflow (must still disarm). The current tests only assert that the query/actor strings exist, and the dedicated script test does not exercise this wrapper-level decision.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

Fixed the workflow-identity gap at exact head 50926c0f48a078a94297a385d46dfd31520e25ac: the stale-rejection proof now resolves this caller run's workflow_id and considers only later runs of that same workflow and PR. New behavior fixtures prove a later trusted same-workflow run reauthorizes, while trusted other-workflow, untrusted same-workflow, newer-untrusted, and other-PR runs do not.

@coderabbitai review this exact head.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review

Fresh substantive review requested for exact head 50926c0f48a078a94297a385d46dfd31520e25ac; the reauthorization proof is now scoped to this caller workflow ID and covered by positive/negative fixtures.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='50926c0f48a078a94297a385d46dfd31520e25ac'

echo '== requested commit and checkout =='
git rev-parse --verify "${target}^{commit}"
git rev-parse HEAD
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== commit delta =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=120 "${target}^" "$target"

echo '== candidate paths =='
fd -HI -t f . | grep -E '(^|/)(enable-auto-merge\.yaml|disarm-auto-merge\.sh|test-disarm-auto-merge\.sh|test-enable-auto-merge-author-gate\.sh|enable-auto-merge-authors\.json|ci\.yaml)$' || true

echo '== workflow outline =='
ast-grep outline .github/workflows/enable-auto-merge.yaml --items all 2>/dev/null || true

echo '== workflow decision path =='
nl -ba .github/workflows/enable-auto-merge.yaml | sed -n '1,360p'

echo '== affected test files =='
for file in \
  .github/tests/test-disarm-auto-merge.sh \
  .github/tests/test-enable-auto-merge-author-gate.sh \
  .github/tests/enable-auto-merge-authors.json \
  .github/tests/disarm-auto-merge-mock-bin/gh
do
  if [ -f "$file" ]; then
    echo "--- $file ---"
    cat -n "$file"
  fi
done

echo '== workflow-run identity/query references =='
rg -n -C 10 --hidden --glob '!.git/**' \
  '(workflow_id|actions/runs|same.workflow|reauthor|stale|supersed|updated_at|pull_request)' \
  .github .scripts README.md 2>/dev/null || true

Length of output: 59446


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='50926c0f48a078a94297a385d46dfd31520e25ac'

echo '== disarm workflow block =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" |
  sed -n '/^  disarm-untrusted-update:/,/^  auto-merge:/p'

echo '== selector and regression test =='
for path in \
  .scripts/find-trusted-superseding-pr-run.sh \
  .github/tests/test-find-trusted-superseding-pr-run.sh \
  .github/tests/superseding-pr-runs.json
do
  echo "--- ${path} ---"
  git show "${target}:${path}"
done

echo '== all relevant fixture and mock assets =='
git ls-tree -r --name-only "$target" |
  grep -E '(superseding|disarm-auto-merge.*mock|mock.*disarm-auto-merge|test-disarm-auto-merge)' |
  while IFS= read -r path; do
    echo "--- ${path} ---"
    git show "${target}:${path}"
  done

echo '== API error handling and fallback references =='
git grep -n -E -C 8 \
  '(gh api|WORKFLOW_ID|run_pages|find-trusted-superseding|LATEST_ACTOR|fail.closed|fail-closed)' \
  "$target" -- .github/workflows/enable-auto-merge.yaml .github/tests .scripts README.md || true

Length of output: 82666


@devantler Found one remaining fail-closed gap.

Line 212 — the new WORKFLOW_ID=$(gh api ...) lookup, and the subsequent paginated run-list request, execute under set -euo pipefail. If either Actions API call fails transiently (or returns malformed data), the step exits before reaching disarm-auto-merge.sh. That leaves any existing classic auto-merge or merge-queue entry armed precisely when the workflow cannot prove a trusted reauthorization.

Please handle metadata/list lookup failure as “no reauthorization proven”: emit a warning and continue to the disarm script. Keep the only skip path limited to a positively identified, newer trusted run of the same workflow and PR. Add a regression fixture or wrapper-level mock asserting that lookup failure invokes revocation rather than aborting first.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

Fixed the fail-closed lookup gap at exact head 578314f091b5c409b8c40243d222d38fa1c59b23. The entire stale-event decision now runs in a tested wrapper: live PR lookup/parse failure, workflow-identity failure, run-list failure, malformed selector data, no match, unrelated workflow, or untrusted latest actor all call the real revocation script. Only a positively selected newer trusted run of the same workflow and PR skips. Wrapper-level mocks assert metadata and pagination failures still execute classic revocation.

@coderabbitai review this exact head.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review

Fresh substantive review requested for exact head 578314f091b5c409b8c40243d222d38fa1c59b23; ordering-proof lookup failures now fall through to tested revocation.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='578314f091b5c409b8c40243d222d38fa1c59b23'

echo '== requested commit and checkout =='
git rev-parse --verify "${target}^{commit}"
git rev-parse HEAD
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== commit delta and whitespace =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=100 "${target}^" "$target"

echo '== relevant candidate files =='
fd -HI -t f . | grep -E '(^|/)(enable-auto-merge\.yaml|disarm-auto-merge\.sh|find-trusted-superseding-pr-run\.sh|handle.*stale.*\.sh|test-.*(disarm|superseding|stale).*\.(sh|json)|ci\.yaml)$' || true

echo '== workflow disarm section =='
sed -n '/^  disarm-untrusted-update:/,/^  auto-merge:/p' .github/workflows/enable-auto-merge.yaml

echo '== scripts that participate in stale-event/revocation decision =='
for path in \
  .scripts/find-trusted-superseding-pr-run.sh \
  .scripts/disarm-auto-merge.sh \
  .scripts/handle-stale-auto-merge-rejection.sh \
  .scripts/disarm-stale-auto-merge.sh
do
  if [ -f "$path" ]; then
    echo "--- $path ---"
    cat -n "$path"
  fi
done

echo '== focused test and fixture files =='
for path in \
  .github/tests/test-find-trusted-superseding-pr-run.sh \
  .github/tests/test-disarm-auto-merge.sh \
  .github/tests/test-handle-stale-auto-merge-rejection.sh \
  .github/tests/superseding-pr-runs.json \
  .github/tests/stale-auto-merge-rejection.json
do
  if [ -f "$path" ]; then
    echo "--- $path ---"
    cat -n "$path"
  fi
done

echo '== references and call sites =='
rg -n -C 6 --hidden --glob '!.git/**' \
  '(find-trusted-superseding-pr-run|disarm-auto-merge|WORKFLOW_ID|actions/runs|workflow_id|reauthor|supersed|stale)' \
  .github .scripts README.md 2>/dev/null || true

Length of output: 150690


@devantler Fresh substantive review completed for exact head 578314f091b5c409b8c40243d222d38fa1c59b23.

No new findings. The stale-event decision is now correctly fail-closed: live-state, workflow-identity, run-list, and selector failures all reach the real revocation script. The sole non-revocation path requires a positively selected later run with the same workflow ID and PR number from a trusted actor. The wrapper-level mocks and selector fixtures cover the important positive and negative ordering cases, while the isolated token-only disarm boundary remains intact.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 578314f091

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .scripts/find-trusted-superseding-pr-run.sh Outdated
Comment thread .github/workflows/ci.yaml
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

Fresh substantive review requested for exact head 20a7fb6b9f8b4f2967b087491e21ebffcbb58920; the selector now binds candidate run creation time to the observed live PR update.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review

Fresh substantive review requested for exact head 20a7fb6b9f8b4f2967b087491e21ebffcbb58920; the B-before-C ordering finding is fixed, and the unsafe live-bot test-hook proposal was answered with the existing safe executable coverage boundary.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='20a7fb6b9f8b4f2967b087491e21ebffcbb58920'

echo '== requested commit and checkout =='
git rev-parse --verify "${target}^{commit}"
git rev-parse HEAD
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== commit delta and whitespace =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=120 "${target}^" "$target"

echo '== relevant candidate paths =='
fd -HI -t f . | grep -E \
  '(^|/)(enable-auto-merge\.yaml|find-trusted-superseding-pr-run\.sh|handle-stale-auto-merge-rejection\.sh|disarm-auto-merge\.sh|test-.*(superseding|stale|disarm).*\.(sh|json)|.*mock.*/gh)$' \
  || true

echo '== workflow stale-rejection call site =='
if [ -f .github/workflows/enable-auto-merge.yaml ]; then
  nl -ba .github/workflows/enable-auto-merge.yaml |
    sed -n '/^  disarm-untrusted-update:/,/^  auto-merge:/p'
fi

echo '== ordering scripts and static test/fixture assets =='
for path in \
  .scripts/find-trusted-superseding-pr-run.sh \
  .scripts/handle-stale-auto-merge-rejection.sh \
  .scripts/disarm-auto-merge.sh \
  .github/tests/test-find-trusted-superseding-pr-run.sh \
  .github/tests/test-handle-stale-auto-merge-rejection.sh \
  .github/tests/superseding-pr-runs.json \
  .github/tests/stale-auto-merge-rejection.json \
  .github/tests/disarm-auto-merge-mock-bin/gh
do
  if [ -f "$path" ]; then
    echo "--- $path ---"
    cat -n "$path"
  fi
done

echo '== all creation/update ordering references =='
rg -n -C 8 --hidden --glob '!.git/**' \
  '(created_at|createdAt|updated_at|updatedAt|LIVE_PR|EVENT_UPDATED|candidate|supersed|reauthor|WORKFLOW_ID|workflow_id)' \
  .github .scripts README.md 2>/dev/null || true

Length of output: 17478


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='20a7fb6b9f8b4f2967b087491e21ebffcbb58920'

echo '== workflow disarm job and wrapper call-site =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" |
  sed -n '/^  disarm-untrusted-update:/,/^  auto-merge:/p'

echo '== stale wrapper test, complete =='
git show "${target}:.github/tests/test-disarm-untrusted-event.sh"

echo '== gh mock, complete =='
git show "${target}:.github/tests/disarm-auto-merge-mock-bin/gh"

echo '== selector test and fixtures, complete =='
git show "${target}:.github/tests/test-find-trusted-superseding-pr-run.sh"
git show "${target}:.github/tests/superseding-pr-runs.json"

echo '== committed references and invocation arguments =='
git grep -n -E -C 6 \
  '(disarm-untrusted-event\.sh|find-trusted-superseding-pr-run\.sh|live_updated_at|event_updated_at|created_at|created=")' \
  "$target" -- .github .scripts README.md || true

echo '== shell static syntax validation (without executing repository scripts) =='
for path in \
  .scripts/disarm-untrusted-event.sh \
  .scripts/find-trusted-superseding-pr-run.sh \
  .github/tests/test-disarm-untrusted-event.sh \
  .github/tests/test-find-trusted-superseding-pr-run.sh \
  .github/tests/disarm-auto-merge-mock-bin/gh
do
  git show "${target}:${path}" | bash -n
  echo "syntax OK: ${path}"
done

Length of output: 144649


@devantler Fresh substantive review completed for exact head 20a7fb6b9f8b4f2967b087491e21ebffcbb58920.

No new findings. The selector now requires a same-workflow, same-PR, trusted candidate run to have been created no earlier than the observed live PR update, so a trusted run from before the newer update cannot suppress revocation. The wrapper retains fail-closed behavior for live-state, workflow-identity, run-list, and selector failures. The new negative ordering fixture covers the B-before-C case, and static Bash syntax validation passed for the modified scripts, tests, and mock.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 20a7fb6b9f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .scripts/find-trusted-superseding-pr-run.sh Outdated
Comment thread .github/workflows/enable-auto-merge.yaml Outdated
@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@coderabbitai review

Please perform a fresh substantive review of exact head af9ce77a86ef6cfcb0fc714cfb43a0172b01568e, including the strict later-run timestamp and original/rerun-triggering actor checks.

@devantler

Copy link
Copy Markdown
Contributor Author

🤖 Generated by the Agentic Engineer.

@codex review

Please review exact head af9ce77a86ef6cfcb0fc714cfb43a0172b01568e, especially rerun initiator trust, same-second ordering, and fail-closed revocation.

@coderabbitai

coderabbitai Bot commented Jul 28, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='af9ce77a86ef6cfcb0fc714cfb43a0172b01568e'

echo '== requested commit and checked-out HEAD =='
git rev-parse --verify "${target}^{commit}"
git rev-parse HEAD
git show --no-ext-diff --format=fuller --no-patch "$target"

echo '== first-parent delta and whitespace check =='
git diff-tree --no-commit-id --name-status -r "$target"
git diff --no-ext-diff --check "${target}^" "$target"
git diff --no-ext-diff --unified=120 "${target}^" "$target"

echo '== candidate files =='
git ls-tree -r --name-only "$target" | grep -E \
  '(^|/)(enable-auto-merge\.yaml|find-trusted-superseding-pr-run\.sh|disarm-untrusted-event\.sh|disarm-auto-merge\.sh|test-.*(superseding|disarm|stale).*\.(sh|json)|.*mock.*/gh)$' \
  || true

echo '== workflow stale-event decision call site =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" | \
  sed -n '/^  disarm-untrusted-update:/,/^  auto-merge:/p'

echo '== ordering and trust scripts =='
for path in \
  .scripts/find-trusted-superseding-pr-run.sh \
  .scripts/disarm-untrusted-event.sh \
  .scripts/disarm-auto-merge.sh
do
  if git cat-file -e "${target}:${path}" 2>/dev/null; then
    echo "--- ${path} ---"
    git show "${target}:${path}" | cat -n
  fi
done

echo '== relevant tests, fixtures, and GitHub CLI mocks =='
git ls-tree -r --name-only "$target" | grep -E \
  '(^\.github/tests/)(test-.*(superseding|disarm|stale).*\.(sh|json)|.*mock.*/gh|.*(superseding|stale).*\.(json|yaml))$' \
  | while IFS= read -r path; do
      echo "--- ${path} ---"
      git show "${target}:${path}" | cat -n
    done

echo '== scoped references to ordering and actor metadata =='
git grep -n -E -C 8 \
  '(created_at|updated_at|run_started_at|triggering_actor|actor|rerun|run_attempt|workflow_id|workflow_run|supersed|reauthor|LIVE_PR)' \
  "$target" -- .github .scripts README.md || true

Length of output: 202231


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target='af9ce77a86ef6cfcb0fc714cfb43a0172b01568e'

echo '== exact first-parent patch =='
git diff --no-ext-diff --unified=80 "${target}^" "$target" -- \
  .github/workflows/enable-auto-merge.yaml \
  .scripts/find-trusted-superseding-pr-run.sh \
  .scripts/disarm-untrusted-event.sh \
  .github/tests/superseding-pr-runs.json \
  .github/tests/test-find-trusted-superseding-pr-run.sh \
  .github/tests/enable-auto-merge-authors.json \
  .github/tests/test-enable-auto-merge-author-gate.sh \
  .github/tests/test-disarm-untrusted-event.sh \
  .github/tests/disarm-auto-merge-mock-bin/gh \
  README.md

echo '== selector: complete =='
git show "${target}:.scripts/find-trusted-superseding-pr-run.sh" | cat -n

echo '== selector fixture names and candidate identity/timestamps =='
git show "${target}:.github/tests/superseding-pr-runs.json" |
  jq -r '.[] |
    .name as $name |
    [$name, .current_run_id, .workflow_id, .pr_number, .live_updated_at,
     (.runs[]?.workflow_runs[]? |
       "id=\(.id), wf=\(.workflow_id), created=\(.created_at), actor=\(.actor.login // ""), rerun=\(.triggering_actor.login // ""), prs=\([.pull_requests[]?.number] | join(","))")
    ] | `@tsv`'

echo '== actor-trust fixtures that distinguish original and rerun initiators =='
git show "${target}:.github/tests/enable-auto-merge-authors.json" |
  jq -c '.[] | select(has("triggering_actor") or (.name | test("rerun|actor"; "i")))'

echo '== workflow classifier and disarm predicates, with source lines =='
git show "${target}:.github/workflows/enable-auto-merge.yaml" |
  nl -ba | sed -n '80,220p'

echo '== mock API routing relevant to workflow run metadata and pagination =='
git show "${target}:.github/tests/disarm-auto-merge-mock-bin/gh" | cat -n

echo '== focused test registrations =='
git show "${target}:.github/workflows/ci.yaml" |
  nl -ba | sed -n '2105,2130p'

Length of output: 62365


@devantler Fresh substantive review completed for exact head af9ce77a86ef6cfcb0fc714cfb43a0172b01568e.

No new findings. The selector now requires a candidate run to be created strictly after the observed live PR update, so equal-second ordering does not authorize a skip. It also requires both the original run actor and triggering_actor to be allowlisted, preventing an untrusted collaborator from re-running a trusted event to reauthorize auto-merge.

The focused fixtures cover same-second ambiguity, pre-update candidates, untrusted rerun initiators, untrusted original actors, unrelated workflows, and different PRs. The only non-revocation path remains a positively proven newer trusted run of the same workflow and PR.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@devantler
devantler marked this pull request as ready for review July 28, 2026 20:17
@devantler
devantler merged commit b87e2e1 into main Jul 28, 2026
142 checks passed
@devantler
devantler deleted the codex/fix-auto-merge-vulnerability-in-workflow branch July 28, 2026 20:18
@github-project-automation github-project-automation Bot moved this from 🫴 Ready to ✅ Done in 🌊 Project Board Jul 28, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af9ce77a86

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/workflows/enable-auto-merge.yaml
Comment thread .github/workflows/enable-auto-merge.yaml
Comment thread .github/workflows/enable-auto-merge.yaml
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: ✅ Done

Development

Successfully merging this pull request may close these issues.

1 participant