Once a secret is pushed to a shared repository it lives in Git history, forks and clones, so every secret scanning alert after the fact starts a rotation, not a clean-up. Push protection moves detection to the moment of the push: the Git server inspects incoming commits and rejects the push if it contains a recognised credential. GitHub, GitLab and Azure DevOps all offer it, with different defaults, bypass mechanics and blind spots, and client-side hooks such as gitleaks add a layer before the push. This guide explains how push protection works on each platform, who can bypass it and how bypasses are recorded, how to cover tokens and service accounts, what to monitor, and a rollout baseline. It is for application security engineers, platform teams and DevSecOps leads. Scanning existing history is covered in the Git Secret Scanning Guide; this page focuses on prevention at push time.
Key takeaways
- Push protection only blocks what it can recognise with confidence. Provider patterns with fixed prefixes (AWS AKIA keys, GitHub tokens, Stripe keys) are blocked; passwords and most generic secrets are not.
- Bypass is the control that matters. On GitHub anyone with write access can bypass by giving a reason unless delegated bypass is on; on GitLab and Azure DevOps a commit message string skips the check.
- A bypass should create evidence. GitHub and Azure DevOps create alerts and GitHub writes an audit log event; route both to the team that rotates secrets.
- It does not see everything. GitLab skips binary files, files over 1 MiB and very large pushes; GitHub scans after the push if the inline scan times out; existing history is out of scope.
- Layer client and server checks. A pre-commit hook catches secrets before they enter local history; the server check is the one a developer cannot switch off.
How push protection works
Push protection runs on the Git server as part of receiving a push (GitLab documents it as a pre-receive check). It scans the new content against a set of high-confidence patterns and, on a match, rejects the push with the commit, file, line and secret type. The developer must remove the secret from every commit that contains it, not just the latest, then push again. A secret that has already been accepted, with an existing alert, is not blocked a second time on GitHub.
| Aspect | GitHub | GitLab | Azure DevOps |
|---|---|---|---|
| Licence | GitHub Secret Protection for repository push protection; push protection for users is free on github.com | Ultimate tier (GA in 17.5) | GitHub Advanced Security for Azure DevOps or GitHub Secret Protection for Azure DevOps |
| Default state | Repository protection off by default; push protection for users on by default for public repositories | Off; Self-Managed and Dedicated admins must first allow it | Built into Azure DevOps Git once the product is enabled |
| Entry points covered | Command-line pushes, web UI commits, file uploads, REST API (Create a blob, Create or update file contents), GitHub MCP server (public repositories) | HTTP(S) and SSH pushes, Web IDE | Command-line and web pushes |
| What is scanned | New content in the push | Diffs of pushed commits only (since 17.11) | New content in the push |
| Bypass | Reason selected in the block message, or delegated bypass approval | Push option secret_push_protection.skip_all or [skip secret push protection] in a commit message | skip-secret-scanning:true in the commit message |
| Bypass evidence | Alert, audit log event, email to owners and watchers | Audit events for skips, blocks and timeouts (19.3 and later) | A secret scanning alert once the secret is pushed |
Which secrets are blocked
| Pattern category (GitHub) | Example | Push protection | Implication |
|---|---|---|---|
| Provider patterns | aws_access_key_id, GitHub personal access tokens | On by default for most | Covers the cloud and SaaS keys attackers test first |
| Generic patterns | RSA private keys, database connection strings | Configurable, off by default | Enable deliberately and measure false positives |
| AI-detected | Passwords | Not supported | Passwords are only found after the push |
| Custom patterns | Internal token formats | Can be enabled per pattern | Give internal tokens a unique prefix so they can be blocked |
GitHub notes that push protection only supports the most recent token versions it can identify with confidence, and Microsoft says the same about legacy token formats on Azure DevOps. GitLab uses only high-confidence rules, so personal access tokens with custom prefixes are not detected. The practical lesson is that token format design matters: credentials with a fixed, documented prefix can be blocked; random strings cannot. See the API Key Management Guide.
Choosing where to enforce
| Control point | Strength | Weakness | Best for |
|---|---|---|---|
| IDE or editor plugin | Feedback while typing | Per developer, easily absent | Developer experience |
| Pre-commit hook | Secret never enters local history | Opt-in and skippable | Teams with shared repository templates |
| Server push protection | Cannot be skipped without a recorded bypass (GitHub) or marker | High-confidence patterns only, size limits | Organisation-wide enforcement |
| Post-push and history scanning | Broadest detection, including passwords and old commits | Secret is already exposed | Backstop and measurement |
Self-hosted Git servers without a built-in feature can approximate push protection with a server-side pre-receive hook that runs a scanner on incoming commits; test its performance on large pushes before enforcing, because a slow hook blocks every developer.
Admin roles and bypass privileges
On GitHub, repository administrators, organisation owners, security managers and enterprise owners can enable push protection. By default every user with write access can bypass a block by choosing a reason, and the reason determines the alert state.
| Bypass reason (GitHub) | Alert created | What to do |
|---|---|---|
| It's used in tests | Closed, resolved as "used in tests" | Sample closed alerts weekly: real secrets are often labelled as test data |
| It's a false positive | Closed, resolved as "false positive" | Review trends per pattern; tune or report the pattern |
| I'll fix it later | Open alert | Treat as an exposed secret: rotate, then remove from history |
- Delegated bypass: restricts who can bypass. Named roles or teams get bypass privileges and review requests from everyone else; requests expire after seven days. Organisation owners, security managers and holders of the "review and manage secret scanning bypass requests" fine-grained permission can always bypass.
- Exemptions: GitHub can exempt actors from push protection entirely, intended for trusted automation such as migration bots. Keep the list short, because an exempt bot account that is compromised pushes anything.
- Configuration at scale: apply push protection through an organisation or enterprise security configuration rather than repository by repository, so new repositories inherit it.
- GitLab and Azure DevOps: skip strings are available to any pusher. Compensate with audit events (GitLab) or alerts (Azure DevOps), pipeline secret detection that rescans after the push, and alerting on the skip markers in commit messages.
Integrations, API tokens and service accounts
The secrets push protection blocks are mostly non-human identity credentials: cloud access keys, CI tokens, API keys and private keys. The platforms' own automation is also a source and a gap.
- Bots and automation that commit: dependency bots, release tools and code generators push commits. Do not exempt them by default; exempt only accounts that legitimately write files which look like secrets, and monitor their pushes.
- REST API writers: integrations that write files through the API receive an HTTP 409 when GitHub detects a secret. Make sure integration code handles the error instead of retrying through another path, and that any use of the "Create a push protection bypass" endpoint is reviewed.
- AI coding agents: agents push through the same Git and API paths and can paste credentials from their context into code. GitHub's push protection covers the GitHub MCP server for public repositories; for private repositories rely on repository push protection. See the GitHub Copilot Security Guide.
- Developer tokens: the push itself is authenticated with a personal access token, SSH key or OAuth token, which is also an NHI credential to manage. See the GitHub Personal Access Tokens Security Guide.
Client-side layer
A pre-commit hook stops the secret before it is ever in a local commit, which avoids the history rewrite that a server block requires. Gitleaks can run through the pre-commit framework (pre-commit install) or as a script in .git/hooks/. Its escape hatches are worth knowing: SKIP=gitleaks disables the hook for one commit, a gitleaks:allow comment suppresses one line, and a .gitleaksignore file suppresses findings by fingerprint. Client hooks are opt-in and can be skipped (for example with git commit --no-verify), so treat them as developer convenience and keep the server control as the enforcement point.
Logging and detection
| Event | Where to find it | Response |
|---|---|---|
| Push blocked | GitHub push protection metrics; GitLab audit event for blocked push | Trend by repository and secret type; target training where blocks cluster |
| Bypass with "I'll fix it later" | GitHub open alert, audit log, email | Rotate the credential within the secret's SLA, then clean history |
| Bypass request pending or approved | GitHub delegated bypass queue and audit log | Reviewers check the secret is not real before approving |
| Skip marker in commit message | Git history, GitLab skip audit events (method, account, project, branch, commits) | Rescan the commits with pipeline detection; follow up with the pusher |
| Scan timed out or push too large | GitLab audit events; GitHub scans after the push | Ensure post-push scanning catches what the inline check skipped |
| Exemption list change | Organisation or enterprise audit log | Alert security; exemptions should change rarely |
The threat being prevented is attackers harvesting credentials from code (ATT&CK T1552.001 Credentials In Files) found by searching public and private repositories (T1593.003 Code Repositories, T1213.003 Code Repositories). In the CISA Private-CISA GitHub leak, administrative AWS GovCloud keys and other credentials sat in a public contractor repository for about six months; keeping push protection enabled was one of the stated lessons.
Hardening baseline
| Control | Setting | Why |
|---|---|---|
| Coverage | Push protection on for every repository through an organisation or enterprise security configuration | New repositories inherit protection |
| Bypass | Delegated bypass with a small security reviewer team (GitHub) | Removes self-service bypass for every writer |
| Exemptions | Only named automation accounts, reviewed quarterly | Exempt actors are invisible to push protection |
| Patterns | Provider defaults plus selected generic patterns and custom patterns for internal tokens | Extends coverage beyond public provider formats |
| Post-push scanning | Secret scanning or pipeline secret detection on all repositories and history | Catches timeouts, skips, binaries and passwords |
| Client hooks | Pre-commit secret scanning in developer templates | Avoids history rewrites |
| Rotation path | Documented owner and rotation runbook for each credential type | A blocked push is cheap; a bypassed one needs action |
Phased rollout
- Measure first: run a full history scan (GitHub offers a free risk report for Team and Enterprise organisations) and fix live secrets before enabling blocks, so developers are not blocked on old leaks.
- Pilot: enable push protection for a few active repositories, publish a short guide on removing a secret from commits (
git commit --amend --allfor the latest commit, interactive rebase for earlier ones). - Organisation-wide: apply through security configurations; GitLab Self-Managed admins first allow the feature, then enable per project or by API.
- Tighten bypass: once false positive rates are known, turn on delegated bypass and limit exemptions.
- Extend patterns: add generic and custom patterns one at a time, checking the metrics for bypass rates after each.
Rollback: push protection can be disabled per repository or pattern without data loss. If a pattern causes widespread false positives, remove it from push protection while keeping it in post-push scanning rather than switching the feature off.
Small and large organisations: a small team on github.com can rely on push protection for users plus repository push protection on its private repositories and a pre-commit hook. A large enterprise needs enterprise-level configuration, delegated bypass with a staffed review queue, custom patterns for internal tokens and SIEM ingestion of bypass and exemption events.
Common misconfigurations
- Relying on push protection for users: it is on by default but only protects pushes to public repositories and creates no alerts on bypass unless repository protection is also on.
- Every writer can bypass: without delegated bypass, "It's used in tests" closes the alert and a real key stays live.
- Treating a bypassed secret as fixed when it is deleted: once pushed, the secret is in history and clones; it must be revoked. GitHub's guidance is to revoke real secrets and remove them from history.
- Unrecognisable internal tokens: internal API keys without a prefix cannot be blocked, so they leak freely.
- Ignoring skip markers: on GitLab and Azure DevOps a commit message string disables the check; nobody watches for it.
- Exempting the CI bot broadly: a compromised automation token then pushes secrets, or secret-stealing code, unchecked.
Framework mapping
| Control area | NIST SP 800-53 Rev. 5 | NIST CSF 2.0 | CIS Controls v8.1 | ISO/IEC 27001:2022 Annex A |
|---|---|---|---|---|
| No embedded static authenticators | IA-5(7) | PR.AA-01 | 16 | A.8.4, A.8.28 |
| Secure configuration of repositories | CM-6 | PR.PS-01 | 4, 16 | A.8.9, A.8.25 |
| Bypass and exemption governance | AC-6, AC-2 | PR.AA-05 | 6 | A.8.2 |
| Alerting and audit of bypasses | AU-2, AU-6, SI-4 | DE.CM-09 | 8 | A.8.15, A.8.16 |
Detection and measurement
- Coverage: percentage of active repositories with push protection enabled.
- Block to bypass ratio: pushes blocked versus bypassed, from push protection metrics, per organisation and per secret type.
- Open "fix it later" alerts: count and age; each one is a live exposure.
- Exemptions: number of exempt actors and days since last review.
- Post-push findings: secrets found by history scanning that push protection should have blocked, which shows pattern or coverage gaps.
Practitioner checklist
- Run a full history scan and rotate live secrets before turning on blocking.
- Enable repository push protection through organisation or enterprise configuration.
- Turn on delegated bypass and name a security review team.
- Limit exemptions to named automation accounts and review them quarterly.
- Add custom patterns for internal token formats and give new tokens a unique prefix.
- Alert on "I'll fix it later" bypasses, skip markers and exemption changes.
- Keep post-push secret scanning on to catch timeouts, large pushes, binaries and passwords.
- Ship a pre-commit secret scanning hook in developer templates.
- Publish a developer guide to removing secrets from commits and to rotation contacts.
Standards and references
- GitHub Docs: Push protection
- GitHub Docs: Delegated bypass for push protection
- GitHub Docs: Supported secret scanning patterns
- GitHub Docs: Working with push protection from the command line
- GitLab Docs: Secret push protection
- Microsoft Learn: Secret scanning for GitHub Advanced Security for Azure DevOps
- Gitleaks: Project documentation
Related NHI Mgmt Group resources: Git Secret Scanning Guide · Secrets Scanning Buyer's Guide · Leaked Credential Response Playbook · AWS Access Keys Security Guide · OWASP NHI Top 10 NHI2 Secret Leakage Guide