1. What It Does
A secret that is pushed once is in the history of every clone, and has to be rotated and purged afterwards. Push protection moves the check to the moment of the push: the server reads everything the push would add and refuses the push when a file version, a commit message or a tag message contains what looks like a secret. It recognizes:
- AWS Access Key ID (
aws-access-key-id) - GitHub Token (
github-token) - Stripe Live API Key (
stripe-live-key) - Slack Token (
slack-token) - Google API Key (
google-api-key) - Private Key Block (
private-key-block) - Generic Assigned Secret (high-entropy) (
generic-assigned-secret)
These are the same rules as the unityfreak/secret-scan commit check, which can only report a secret after it is already in history. A match is a reason to look, not proof — the override below exists for false positives.
Only content that is new in the push is scanned: a file version that is already reachable from an existing branch or tag is not scanned again. A file is scanned as a whole, not just the changed lines, so editing a file that already contains a secret is rejected until the secret is removed (or the push is overridden).
2. What a Rejection Looks Like
The message arrives with your git push, over HTTPS and over SSH alike:
remote: UnityFreak: push rejected - it adds what looks like a secret (1 finding(s) ...):
remote: UnityFreak: config/prod.env:2 - aws-access-key-id (blob 1a2b3c4)
remote: UnityFreak: Remove the secret from EVERY commit that adds it ...
! [remote rejected] main -> main (pre-receive hook declined)
Each finding is the file, the line, the rule and the first seven characters of the file version's object id (git cat-file -p 1a2b3c4 in your clone shows it). Nothing in the message, the server logs or the audit log contains the matched text. A push with several branches or tags is rejected as a whole: none of its refs moves.
A push that is too big to scan within the limit (more than 400,000 new objects, 1 GiB of text to read, or 100 seconds) is rejected too, with the reason — push the commits in several smaller pushes (git push <remote> <commit>:<branch>). If the scan cannot run, the push is refused and can be retried. Two narrow moments are the exceptions: for the minutes of a platform update the check can be skipped, and a push that was already being scanned when an owner switched protection off is allowed.
3. Fixing a Rejected Push
- Rotate the secret if it was ever real — treat anything that has left your machine in a commit as exposed.
- Remove it from every commit that adds it. Deleting it in a later commit does not help: the earlier commit is part of the push. For recent commits use
git rebase -i (edit the commit that added it) or git commit --amend; for a long history use git filter-repo. - Keep real secrets in environment variables or a secrets manager, and commit only a template (
.env.example). - Push again.
4. The Audited Override
For a false positive (a test fixture, a documented example key) or a risk you accept, a person the repository allows can push past one block with a reason:
git push -o secret-scan-bypass="test fixture, not a real key" origin main
- The reason must be 8 to 300 printable characters, with no invisible or direction-changing characters, and it must not itself contain what looks like a secret (describe why the override is safe instead of pasting the value). It is recorded in the audit log (
secret_push_bypassed) with who pushed, which refs and which findings were overridden — but not the secret. A rejected push is recorded too (secret_push_blocked). - It works over HTTPS and SSH (push options need Git 2.10 or newer on your side). It covers that one push; the next push is checked again.
- Who may use it is a repository setting: repository admins (default), anyone with write access, or nobody. An organization ruleset can disable it for all its repositories.
- A deploy key can never override, and neither can auto-merge or the merge queue: nobody is there to give a reason.
- A blocked web edit or pull request merge shows the same findings and, for a person who may override, a reason field; submitting again with a reason commits or merges it and records it. Creating a branch or tag from a commit that no branch or tag holds yet takes the same reason in the
secretScanBypassReason field of the request. - The same override covers a push or merge that could not be scanned because it was too large — after you have decided that is acceptable.
5. What Is and Isn't Scanned
Not every way content reaches a repository can be scanned the same way, and the guarantee is only as wide as this list. The repository's Settings page shows the same list.
- git push (HTTPS and SSH)scannedBefore any branch or tag moves, the pre-receive hook scans every file version, commit message and tag message the push makes newly reachable in the repository - including intermediate commits: a secret that one commit adds and a later commit removes is still rejected. The whole file is scanned, not just the changed lines, so editing a file that already contains a secret is rejected until the secret is removed. Content that is already reachable from an existing branch or tag is not scanned again. A rejected push shows only the file, the line and the rule - never any part of the secret.
- Web editor and applied review suggestionsscannedThe new content of the edited file and the commit message are scanned before anything is written; a block shows the rule and the line, never the matched text.
- Pull request merge (merge, squash, rebase)scannedThe commit the landing creates is scanned before the target branch moves, like the tip of a push, and it is built where nothing can read it: the repository stores it only after the scan has passed. What is scanned is what the landing makes newly reachable. Squash: the files that differ from the target branch (also when the pull request comes from a branch of this repository) and the commit message, which is the pull request title and description. Rebase: every replayed commit - its message and the files it adds. Merge: the merge commit and its message, anything only the merge itself introduces, and the pull request's own commits when no branch or tag holds them yet (a pull request from a fork). A merge of a branch of this repository does not scan that branch's own commits again: they are published history, exactly as for a push. A secret that was already on the target branch before protection was switched on never blocks an unrelated pull request.
- Auto-mergescannedScanned when the pull request lands, like a manual merge. Auto-merge cannot override a block: it stays armed and lands by itself once the secret is gone from the pull request (or an admin changes the setting).
- Merge queuescannedScanned when the queued candidate lands, like a manual merge. A pull request that adds a secret leaves the queue; the queue cannot override a block.
- Resolving pull request conflicts in the browserscannedThe hand-written resolution of each conflicted file is scanned before it is committed.
- Creating a branch in the web app or APIscannedThe start commit is scanned like the tip of a push of the new branch. Branching from a branch, a tag or a commit on one lists nothing new and is free; a commit that no branch or tag holds yet (a pull request head from a fork) is scanned first, and a block can be overridden like a push.
- Creating a tag or release in the web app or APIscannedThe target commit is scanned like the tip of a push of the new tag (free when a branch or tag already holds it), and the message of an annotated tag is scanned too; a block can be overridden like a push.
- Creating the first README of an empty repositorynot scannedWrites a README generated by the platform from the repository name and description.
- Import from URLnot scannedCopies the source history once, before the repository accepts pushes. A secret that sits in years of old upstream history would make the import impossible to finish; run a history scan and rotate any exposed key instead. The post-push secret-scan check still reports new commits.
- Pull mirror syncnot scannedSync replicates the upstream repository exactly; it cannot be selective. Pushes are refused while a pull mirror is enabled.
- Push mirror syncnot scannedWrites to the external mirror target, never to this repository.
- Forkingnot scannedCopies existing history into a new repository; nothing new is introduced.
- Generating a repository from a templatenot scannedCopies the files of a template at one commit into a new repository as a single first commit; nothing new is introduced - the person generating it can already read every file. The template's own pushes were scanned if its owner switched protection on, and the new repository starts with push protection on, so what is pushed to it afterwards is scanned. A secret already committed to a template is copied with it: rotate it and remove it from the template.
- Sync forkscannedThe upstream commits a sync brings in are scanned like a push of them to the fork's default branch (only what is newly reachable in the fork, commit messages included) before the branch moves; the people the repository's setting allows can override with a recorded reason. The commits already exist in the upstream repository, but a secret in them would become part of this repository's history, so it is blocked here as it would be for a push.
- Dependency fix pull requests (the dependency bot)not scannedNot scanned: the commit changes only a lockfile and the manifests next to it, derived from the existing files plus public-registry metadata (versions, resolved URLs, integrity digests); no free-form content is written. Merging the pull request is an ordinary pull request merge and is scanned as such.
- Cross-repository pull request syncnot scannedStores a fork pull request head in an internal ref only. That content reaches a branch or tag through a merge or rebase of the pull request (scanned) or a push of the commit (scanned: a commit that is only in an internal ref counts as new).
- Wiki editsnot scannedThe wiki is a separate repository edited on the web; it is not scanned.
- Git LFS objectsnot scannedLFS objects are stored outside the repository and never pass through the push; only the small pointer file is scanned. A secret inside a large file kept in LFS is not detected.
- Binary filesnot scannedA file with a NUL byte in its first 8,000 bytes (images, archives, compiled assets, UTF-16 text without a byte-order mark, UTF-32 text) is skipped: the rules match text. UTF-16 text that starts with a byte-order mark (what Windows editors write) is decoded and scanned. A secret inside a binary file or a compressed archive is not detected.
- Very large text filesnot scannedA text file larger than 32 MiB is not read (the push is allowed and the pusher is told how many files were skipped). A push whose scan would need more than 1 GiB of reads, 400,000 objects or 100 seconds is rejected instead (push it in smaller pushes); a person the bypass setting allows can override that rejection.
- Very large commit and tag messagesnot scannedA commit or tag message larger than 1 MiB is not read (counted, and the push is allowed).
- Secrets in formats the rules do not matchnot scannedDetection is pattern based: seven rules (provider tokens with a fixed prefix, private key blocks, and a quoted password / token / key assignment with a high-entropy value). Not detected: unquoted values in .env files (DB_PASSWORD=...), AWS secret access keys on their own (only the access key id has a fixed shape), passwords in connection strings, and anything encoded, encrypted or split over several lines.
- File names, author and committer lines, ref namesnot scannedOnly file contents and commit and tag messages are read. A secret placed in a file name, an author or committer name or e-mail, or a branch or tag name is not detected.
- Content that was already in the repositorynot scannedTurning protection on does not scan history that is already stored; use the post-push secret-scan check or a history scanner for that, and rotate anything it finds.
Detection is pattern-based. A secret in an unusual format, an encoded or encrypted value, or one split over several lines is not recognized; push protection is a safety net, not a guarantee, and does not replace rotating a leaked key.
6. Settings & Organization Rules
Repository admins set Settings → Secret push protection: blocking on or off, and who can override. Changes are recorded in the audit log with the old and new value; turning protection off or opening the override up is marked as a relaxation.
Existing repositories. Repositories created before this feature are never switched on for you — nothing is blocked by surprise. Turn it on in the repository's settings, or ask an organization admin to require it for all matching repositories.
Organizations. An organization ruleset (Organization → Rulesets) can require secret push protection for every repository it targets, so a repository admin cannot turn it off, and can disable the override for them. Like all rulesets, these only ever make a repository stricter.