Race condition in remove_approvals_with_new_commit allows merge before approvals are removed

Everyone can contribute. Help move this issue forward while earning points, leveling up and collecting rewards.

Summary

Race condition in remove_approvals_with_new_commit allows automated merge commands to bypass approval requirements by executing before approvals are asynchronously removed.

Steps to reproduce

Prerequisites:

  • GitLab version 18.0 (self-managed)
  • Project with approval policy configured with remove_approvals_with_new_commit set to true
  • Using glab CLI for automated merge operations

Steps:

  1. Push a commit to a merge request
  2. Obtain approval for the merge request
  3. Wait for all checks to pass and MR to show as "green/ready"
  4. Immediately push a new commit and attempt to merge using rapid automation:
git push && glab mr merge --auto-merge=false -y <MR ID>
  1. Repeat steps 3-4 multiple times

What is the current bug behavior?

When using rapid automated commands (git push && glab mr merge), the merge operation occasionally succeeds before the approval removal process completes. This allows unapproved code to be merged into protected branches, bypassing the remove_approvals_with_new_commit safeguard.

The inconsistent behavior (mostly failing with 405, occasionally succeeding) suggests the approval removal is happening asynchronously in a background process, creating a race condition window where:

  • The push operation completes and returns
  • The merge API call is executed immediately after
  • The merge is accepted before the async approval removal job processes the new commit
  • The merge completes with the new, unapproved commit included

Note that under normal human-operated conditions (with natural delays between push and merge), the feature works correctly

What is the expected correct behavior?

The merge operation should always be rejected when a new commit is pushed to a merge request that has remove_approvals_with_new_commit enabled, regardless of timing or how quickly the merge command is executed. The approval removal should be synchronous or the merge validation should check for pending approval state changes before allowing the merge to proceed.

Specifically:

  • Pushing a new commit should immediately invalidate existing approvals before the push operation returns, OR
  • The merge API should validate that no approval state changes are pending before accepting a merge request, OR
  • The merge operation should be blocked until all async approval removal processes complete

The current implementation creates a security vulnerability where automated tooling or scripts can potentially bypass approval requirements through timing manipulation.

Results of GitLab environment info

Expand for output related to GitLab environment info

 (For installations with omnibus-gitlab package run and paste the output of: \`sudo gitlab-rake gitlab:env:info\`)  (For installations from source run and paste the output of: \`sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production\`)  

Results of GitLab application Check

Expand for output related to the GitLab application check

(For installations with omnibus-gitlab package run and paste the output of: `sudo gitlab-rake gitlab:check SANITIZE=true`)

(For installations from source run and paste the output of: `sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`)

(we will only investigate if the tests are passing)

Edited by 🤖 GitLab Bot 🤖