GitHub push protection blocked your push (GH013: Push cannot contain secrets)
You ran git push and got GH013: Repository rule violations found with Push cannot contain secrets. GitHub secret scanning found something that looks like a real credential in one of the commits you were pushing, and push protection rejected the whole push. The output looks like this (shortened):
remote: error: GH013: Repository rule violations found for refs/heads/main.
remote:
remote: - GITHUB PUSH PROTECTION
remote: —————————————————————————————————————————
remote: Resolve the following violations before pushing again
remote:
remote: - Push cannot contain secrets
remote:
remote: (?) Learn how to resolve a blocked push
remote: https://docs.github.com/code-security/secret-scanning/...
remote:
remote: —— GitHub Personal Access Token ——————————————————————
remote: locations:
remote: - commit: 8728dbe67
remote: path: config/settings.py:4
remote:
remote: (?) To push, remove secret from commit(s) or follow this URL to allow the secret.
remote: https://github.com/OWNER/REPO/security/secret-scanning/unblock-secret/...
remote:
To github.com:OWNER/REPO.git
! [remote rejected] main -> main (push declined due to repository rule violations)
error: failed to push some refs to 'github.com:OWNER/REPO.git'
The short answer: the push was rejected, so the commits are not on GitHub. Deleting the secret in a new commit does not fix it, because the push still contains the older commit that added it. You have to rewrite your unpushed commits so none of them contains the secret, then push normally. No force push is needed, because you are only changing commits GitHub never accepted.
1. Find the commits that contain the secret
The locations block lists each commit and path:line where the secret was found. GitHub shows up to five detected secrets at a time, so there may be more after you fix these. To see every commit you are about to push, compare your branch with its remote branch (replace origin/main with your branch's upstream):
git log --oneline origin/main..HEAD
To narrow that to commits that touched the flagged file:
git log --oneline origin/main..HEAD -- config/settings.py
Search by file rather than by the secret's value. Typing the key into git log -S puts it in your shell history, which is another copy to clean up.
2. Move the secret out of the code
Replace the hard-coded value with an environment variable or a config file that git ignores. If the flagged file is a .env file, stop tracking it and ignore it. git rm --cached removes it from the index and leaves it on disk:
git rm --cached .env
echo ".env" >> .gitignore
git add .gitignore
Then rewrite the commits with whichever of the next three options fits.
3. If only your latest commit has the secret: amend it
After editing the file, fold the fix into the last commit. This is GitHub's documented fix:
git commit --amend --all
--all stages changes to files git already tracks, so it picks up your edit. If you used git rm --cached and staged .gitignore as in step 2, run git commit --amend --no-edit instead. Check that the file or line is gone with git show --stat HEAD, then git push.
4. If several unpushed commits have it: squash them with reset --soft
If you do not need to keep the individual commits, the simplest route is to undo them while keeping all their changes staged, remove the secret once, and make a single new commit:
git reset --soft origin/main
git status
All the work from those commits is now staged and nothing is lost. Remove the secret (edit the file, or git rm --cached .env), then commit and push:
git commit -m "Add settings loader"
git push
git reset --soft HEAD~3 does the same for the last three commits if you would rather count. Never use --hard here: it discards the changes instead of keeping them staged.
5. If you want to keep your commits: interactive rebase
To fix the commit in place and keep the rest of your history, follow GitHub's steps:
- From the error message and
git log, find the earliest commit that contains the secret, for example8728dbe67. - Start an interactive rebase from its parent:
git rebase -i 8728dbe67~1 - In the editor, change
picktoediton that commit's line, then save and close. - Git stops at that commit. Remove the secret from the file, then run
git add .andgit commit --amend. - Run
git rebase --continue. If later commits also contain the secret, mark each of themeditas well, or resolve the conflicts as git replays them. - Run
git push.
One catch: if the commit did nothing except add the secret file, amending it leaves it empty and git refuses with You asked to amend the most recent commit, but doing so would make it empty. In that case change pick to drop for that commit instead of edit, as long as no later commit changes the same file.
Before pushing, read the diff of everything you are about to send and confirm the value is gone:
git log -p origin/main..HEAD
6. The bypass URL: when it is the right call
The URL after To push, remove secret from commit(s) or follow this URL to allow the secret opens a page where you can allow this one secret. It only works for the user who made the push; anyone else gets a 404. Depending on the repository's settings, you pick a reason:
- It's used in tests: a fixture or sample value that grants no access.
- It's a false positive: the string is not a secret at all.
- I'll fix it later: the secret is real and you are pushing it anyway.
Click Allow me to push this secret, then run git push again within three hours, or you have to repeat the process. When the repository has secret scanning enabled, the bypass is not silent: GitHub creates a secret scanning alert (closed for the first two reasons, open for the third) and notifies the repository's admins and owners, with the reason you chose.
The first two reasons are what the bypass is for. "I'll fix it later" means the live credential goes into the repository, where everyone with read access, every clone and every fork can see it, and removing it later means rewriting pushed history (see how to remove an API key from git history). It is almost always cheaper to spend two minutes on step 3 or 4. If you did push a real secret, revoke it at the provider: what to do when you leak an API key.
If you see no bypass option, your organization uses delegated bypass. The same page lets you submit a request with a comment. If it is approved, you can push that secret, including in future commits. If it is denied, you have to remove it from the commits.
7. Do you need to rotate the key?
Push protection stopped it, so the secret did not land on GitHub. Rotate it anyway if it went anywhere else: another remote, a CI system, a chat message or a pasted log, or if you or a teammate used the bypass. If the key never left your laptop, moving it out of the code is enough.
8. Other things that trip push protection
- Pushing more than you meant to. If your
push.defaultsetting pushes several branches at once, a secret on another local branch can block the push. Push one branch explicitly, for examplegit push origin my-branch, to see which one is the problem. - Public repositories without secret scanning. Push protection for users is on by default for your account, so pushes of supported secrets to any public repository are blocked, even when the repository itself has not turned anything on. Bypassing there does not require a reason.
To catch the secret before it is even committed, run a scanner as a pre-commit hook: see how to scan for secrets before you push. Push protection only recognizes supported secret formats, so a hook plus a gitignored .env is a better first line.