skip to content

GitHub push protection blocked your push — what are your options and what does each cost?

level: middleimportance: must knowfreq 55%

answer

  1. Every commit in the push is checked
  2. A deletion commit on top is not enough
  3. Unblocking demands a stated reason
  4. Someone else may have to approve it
  5. A bypassed live key is now a leaked key

basics

~20 s

Either remove the secret from every commit in the push and push again, or follow the unblock URL and bypass with a reason. Bypassing lets the credential land, creates an alert, and is recorded — so the credential must then be treated as leaked.

solid answer

~50 s

The rejection message names the secret type, the commit SHA and the file and line, and offers an unblock URL. Two real paths. **Fix it:** the secret must be gone from *every* commit in the push, not only from the working tree — deleting it in a new commit on top leaves the original commit in the push and it will be blocked again, so you rewrite the offending commits locally before re-pushing. **Bypass it:** the unblock URL asks for a reason — that it is used in tests, that it is a false positive, or that you will fix it later. The push then succeeds, and GitHub records the bypass and raises a secret scanning alert. In organisations using **delegated bypass**, the request goes to a designated reviewer group for approval instead of being self-service. The cost is simple: once a real credential is bypassed into history it is leaked, and the only correct follow-up is revocation.

code

console · 18 lines
console
$ git push origin main
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote:
remote: - GITHUB PUSH PROTECTION
remote:   Resolve the following violations before pushing again
remote:
remote:   - Push cannot contain secrets
remote:
remote:     Amazon AWS Access Key ID
remote:       locations:
remote:         - commit: 4f2a1c9e0b7d3a5c8e1f6b2d9a4c7e0f3b5d8a1c
remote:           path: src/config.ts:14
remote:
remote:     To push, remove the secret from the commit(s), or follow this URL
remote:     to allow the secret:
remote:     https://github.com/OWNER/REPO/security/secret-scanning/unblock-secret/ID
remote:
error: failed to push some refs to 'https://github.com/OWNER/REPO'

go deeper

for a junior

Know that GitHub can refuse a push containing a credential, that the message tells you which commit and file, and that the safe fix is to take the secret out rather than to click through the bypass.

for a middle

Explain that all commits in the push are evaluated, so a follow-up deletion commit does not help, and name the bypass reasons and the alert a bypass generates.

for a senior

Show the incident judgment: bypass only for non-live values, rotate first for real ones, and use bypass-reason patterns as a signal about fixtures or process rather than blaming individuals.

for a principal

Own the policy: where delegated bypass is worth its latency, how bypass data is reviewed, and how you keep the control from being disabled because the fast path was never made easy.

## Reading the block A push protection rejection is a server-side refusal, so it arrives as `remote:` output from the push. It tells you four things: that a repository rule violation stopped the push, the human name of the secret type detected, the commit and path/line where the match sits, and a URL to unblock. That level of detail is deliberate — the fastest fix requires knowing *which* commit is dirty. ## Option one: remove the secret The critical mechanic is that push protection evaluates **all commits in the push**, not the resulting tree. A developer who notices the block, deletes the line, and commits the deletion will be blocked again, because the earlier commit that introduced the key is still part of what is being pushed. The remedy is to rewrite the offending commits so the credential is not in any of them, then push again. That is ordinary Git work — amend or interactive rebase — and it is easy while the commits are still local and unshared, which is precisely why push protection is worth so much more than post-hoc scanning. While you are there, replace the credential with something the codebase can carry safely: reading from the environment, or a placeholder that is obviously not a key. ## Option two: bypass The unblock flow requires a reason, and the options map to real situations: - **It's used in tests** — the value is a fixture, not a live credential. - **It's a false positive** — the string matches a pattern but is not a credential at all. - **I'll fix it later** — you accept it is real and are choosing to unblock now. Whichever you pick, the push succeeds and GitHub records the bypass; a secret scanning alert is raised for the value, so it appears in triage rather than vanishing. The reason matters operationally because bypass reasons are reviewable: a repository whose bypasses are overwhelmingly "used in tests" has a fixture problem, while one full of "fix it later" has a process problem. ## Delegated bypass Organisations that do not want bypass to be self-service enable **delegated bypass for push protection**. The developer's unblock attempt becomes a **request**, routed to a designated bypass list — typically a security team or repository admins — who approve or deny it. This converts an individual decision made under deadline pressure into a reviewed one. The tradeoff is latency: someone has to be available, or pushes stall. Most organisations run it on their highest-risk repositories rather than everywhere. ## What the bypass actually costs If the value was genuinely a fixture or a false positive, the cost is one alert to close with the matching resolution. If it was real, the cost is a leak: it is now in the repository's history, in every clone and fork made afterwards, and potentially in CI logs and mirrors. Rewriting history later does not undo any of that. The only response that changes the credential's risk is revoking and reissuing it — the block you bypassed was the last cheap moment. This is the point interviewers listen for. A candidate who says "I'd click bypass and clean it up later" has misunderstood the control. The correct answer under deadline pressure is: bypass only if the value is not a live credential; if it is live, rotate it first — a key you have already revoked is no longer a secret, and then the block is trivially resolved. ## Things that do not work - Force-pushing around the check: push protection is evaluated on the push itself, not on branch state. - Adding the file to `.gitignore`: it only affects untracked files going forward and does nothing about commits already in the push. - Excluding the path from alerting: path exclusions shape which matches raise alerts; they are not a way to make push protection ignore a live credential. ## Guidance for teams Make the fast path obvious. Most blocks are honest mistakes by people who now have a broken push and a deadline, and the quality of your outcome depends on whether the easy option is "rewrite the commit and move on" or "click bypass". Document the rewrite recipe somewhere findable, keep obviously-fake fixture values in tests so the fixture case stops arising, and review bypass reasons periodically so you find the systemic causes instead of the individual ones.

  • A developer deletes the key, commits the deletion, and pushes again — but is blocked again. Why?
    Push protection evaluates every commit in the push, not the final tree. The original commit that introduced the credential is still part of what is being sent, so it still matches. The fix is to rewrite the offending commits so the credential appears in none of them, then push.
  • What is delegated bypass for push protection, and when would you turn it on?
    It converts self-service unblocking into a request that a designated reviewer group must approve. You enable it where the cost of a leaked credential is highest — repositories touching production or customer data — and accept the latency. Applying it everywhere tends to create pressure to disable push protection altogether.
  • You bypass a block for a real production key because a release is going out. What now?
    Treat it as a leak, immediately. Revoke and reissue the credential, check the provider's audit log for use, then resolve the resulting secret scanning alert as revoked. Removing it from history afterwards is hygiene, not remediation — the value was exposed the moment it landed.

saying these in an interview costs you the question

  • Says just delete the line and commit on top
  • Treats bypass as the normal way to unblock work
  • Thinks force-pushing evades push protection
  • Believes a bypass leaves no record
  • Rewrites history instead of revoking a live key

context