fix(ng-dev): verify validated head SHA before merging a pull request - #3911
fix(ng-dev): verify validated head SHA before merging a pull request#3911herdiyana256 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces validation to ensure that a pull request's head commit has not changed between validation and merging, preventing the landing of unreviewed commits. It adds a new MismatchedPullRequestHeadShaFatalError and checks the fetched head SHA against the expected SHA in MergeStrategy#prepare, as well as pinning the API merge to the validated head SHA. Feedback suggests updating the error handling in GithubApiMergeStrategy to gracefully handle HTTP 409 errors from GitHub when the head SHA mismatches during the merge call.
| // Pin the merge to the head commit that was validated. `pulls.merge` otherwise merges | ||
| // whatever the pull request head currently is, so a commit pushed after validation but | ||
| // before this call would be merged without review or CI. With `sha` set, GitHub rejects | ||
| // the merge (HTTP 409) if the head has moved since it was validated. | ||
| sha: headSha, |
There was a problem hiding this comment.
Since you are now passing sha: headSha to pulls.merge, GitHub will reject the merge with an HTTP 409 status code if the head has moved since validation.
Currently, the catch block below (around line 139) only handles 403 and 404 errors:
if (isGithubApiError(e) && (e.status === 403 || e.status === 404)) {
throw new FatalMergeToolError('Insufficient Github API permissions to merge pull request.');
}If a 409 occurs, it will be rethrown as a raw GitHub API error, which results in an unhelpful error message and stack trace for the user.
Consider updating the catch block to also handle 409 and throw a user-friendly error (e.g., MismatchedPullRequestHeadShaFatalError or a descriptive FatalMergeToolError).
The merge tooling validates a pull request (approvals, CI status, target labels) against the head commit the GitHub API reports when the PR is loaded and stores it as `pullRequest.headSha`, but the value was never enforced. Both merge strategies then operate on the live PR head instead: `MergeStrategy.prepare` fetches the mutable `pull/<number>/head` ref into a local branch and the merge proceeds from there, and `GithubApiMergeStrategy` calls `pulls.merge` without a `sha`. A commit pushed to the pull request after validation but before the fetch or API call is therefore merged without ever being reviewed or checked. `prepare` now resolves the fetched head and fails closed unless it equals the validated `headSha`, and the API merge strategy passes `headSha` to `pulls.merge` so the server rejects the merge if the head has moved.
a3b54f5 to
0f690ac
Compare
ng-dev pr mergevalidates a pull request (approvals, CI status, target labels) against the head commit the GitHub API reports when the PR is loaded, and stores it aspullRequest.headSha. That value was never checked again, so both merge strategies operate on the live pull request head instead of the commit that was validated:MergeStrategy.preparefetches the mutablepull/<number>/headref intomerge_pr_head, and the autosquash strategy rebases and cherry-picks from there.GithubApiMergeStrategycallspulls.mergewithout asha, so GitHub merges whatever the head currently is.Between the validation read and the fetch (or the API call) the pull request author can push a new commit. The merge then lands that commit even though it was never reviewed or run through CI, a time-of-check/time-of-use gap.
preparenow resolves the fetched head and fails closed unless it equals the validatedheadSha, and the API merge strategy passesheadShatopulls.mergeso the server rejects the merge with a 409 if the head has moved since validation. A unit test covers both the mismatch (rejected) and match (allowed) cases.