PRACTITIONER GUIDE
Practitioner Guide12 min read

How to Stop Developers from Pushing Secrets to Git: Pre-Commit Hooks, Gitleaks, and Push Protection

1 in 10
public GitHub repositories contain at least one committed secret, with AWS keys and database credentials among the most commonly exposed types
170+
secret pattern signatures Gitleaks detects out of the box, covering all major cloud providers, database connection strings, and API key formats
200+
service provider token formats covered by GitHub push protection, blocking secrets at the remote before they enter repository history
5 min
typical window before automated bots index a newly pushed public repository secret, after which rotation is the only valid remediation regardless of history rewriting

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

Secrets in Git are a persistent problem despite being well-understood because the prevention requires behavioral change — and behavioral change requires automation. Telling developers 'don't commit credentials' without tooling that enforces it produces the same result as telling people 'don't run red lights' without traffic lights: compliance depends on attention in the moment when attention is focused elsewhere.

The effective prevention architecture uses three layers: local pre-commit hooks that interrupt commits before they complete (fastest feedback, developer-friendly), repository host push protection that blocks pushes at the remote (backstop for hook bypasses), and CI/CD pipeline secret scanning that validates every pull request regardless of local configuration. This guide covers implementing all three layers and the response workflow for secrets already in history.

Layer 1: Gitleaks pre-commit hook

Pre-commit hooks are the earliest and most developer-friendly intervention point in the secrets prevention stack because they fire before the commit is recorded in git history, giving the developer immediate feedback with the file path and line number of the detected secret while the context is still fresh. Gitleaks, managed through the pre-commit framework, is the most practical implementation: the .pre-commit-config.yaml file lives in the repository, hook installation is a single command per clone, and custom .gitleaks.toml allowlist rules handle test fixture false positives without disabling detection for the broader pattern. The pre-commit framework also handles hook versioning, so all developers automatically use the same Gitleaks version as long as they run pre-commit autoupdate. The critical gap in pre-commit hooks is the --no-verify bypass, which means the CI enforcement layer described in the second list item below is not optional if you want reliable coverage.

Installation and configuration

Repository-level setup: create .pre-commit-config.yaml in the repository root with the Gitleaks hook configuration. Add a custom .gitleaks.toml if you need to customize patterns (add internal secret formats, allowlist false-positive strings): [allowlist] regexes = ['EXAMPLE_KEY_DO_NOT_USE', 'test_api_key_.*']. The allowlist prevents Gitleaks from flagging test fixtures and example configuration files that intentionally contain placeholder credential formats. Commit both .pre-commit-config.yaml and .gitleaks.toml to the repository. Developers run pre-commit install once per clone to register the hook.

CI enforcement for developers who skip hook installation

Add a CI job that runs Gitleaks on every pull request to catch commits from developers who bypassed local hooks. GitHub Actions example: name: Secret Scan; on: [pull_request]; jobs: scan: runs-on: ubuntu-latest; steps: - uses: actions/checkout@v4; with: fetch-depth: 0; - uses: gitleaks/gitleaks-action@v2; env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}. The fetch-depth: 0 is critical: it fetches the complete git history so Gitleaks can scan all commits in the PR, not just the latest. Set this job as a required status check on your main/master branch protection rule so the PR cannot be merged if secrets are detected.

Developer onboarding: making hook installation standard

Include pre-commit install as a step in your developer environment setup script (Makefile, shell script, or dev container setup). Example in a Makefile: setup: pip install pre-commit; pre-commit install; echo 'Pre-commit hooks installed'. For containerized development environments (dev containers, Codespaces): add pre-commit install to the postCreateCommand in devcontainer.json. This ensures every developer who sets up the standard environment has hooks installed without a separate explicit step. Track hook installation compliance by adding a CI job that fails if pre-commit is not configured: pre-commit run --all-files should pass on CI for every commit.

Layer 2: Repository host push protection and secret scanning

Repository host controls are the server-side backstop that catches secrets pushed by developers who bypassed or skipped local pre-commit hooks. GitHub push protection and GitLab's Secret Detection CI template both block or flag secrets before they become part of the default branch history accessible to all repository collaborators. These controls are important because they are not bypassable by individual developer choices, unlike pre-commit hooks: a developer cannot push a secret-containing commit to a protected branch without either triggering a block or generating an audited bypass record. Historical scanning, available through both GitHub's secret scanning alerts view and Gitleaks running in detect mode locally, identifies credentials that were committed before these controls were in place and must be rotated. The combination of push protection for new commits and historical scanning for existing exposure provides full coverage of the repository's secret risk profile.

GitHub: enable push protection and secret scanning organization-wide

In GitHub organization settings: Security > Code security and analysis > Secret scanning > Enable for all repositories. Enable Push protection under the same section. For granular control by repository, use GitHub Advanced Security (GHAS) per-repository settings. Push protection blocks git push operations when the pushed commits contain secrets from GitHub's pattern database (covering 200+ providers). When a secret is blocked, the developer sees the affected commit, the secret type detected, and a link to bypass with a documented reason. Bypass events are logged in the organization audit log and can trigger security team notifications via webhook.

GitLab: Secret Detection CI/CD template

Add GitLab's built-in secret detection to your CI/CD pipeline: include: - template: Security/Secret-Detection.gitlab-ci.yml. This runs the Gitleaks-based scanner on every pipeline execution. For blocking behavior: add the secret detection job as a required job in the merge request approval rules. GitLab Ultimate includes push protection at the repository level equivalent to GitHub's capability. Configure secret_detection_excluded_paths if you have directories with intentional test fixtures. Review findings in Security > Vulnerability Report for a centralized view of all detected secrets across all projects.

Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

The bottom line

The complete secret prevention stack is: Gitleaks pre-commit hook (catches at commit time), CI job running Gitleaks on PRs (catches hooks bypassed with --no-verify), and repository host push protection (catches pushes from unconfigured local environments). Scan existing repository history with gitleaks detect on all branches to identify credentials already exposed. When a secret is found in history, rotate immediately — history rewriting is a cleanup step, not a remediation. Build the stack once, enforce through CI required status checks and branch protection, and make developer onboarding include pre-commit install to keep the local layer working without manual maintenance.

Frequently asked questions

Why do developers keep committing secrets to Git even after being told not to?

The root cause is not negligence but workflow friction: developers working quickly copy-paste credentials into code to test functionality, intending to remove them before committing, and then forget. The commit happens in a flow state where the developer is focused on making the code work. Without automated tooling that interrupts the commit to flag the secret, the human intention to remove it is not reliable. Prevention requires eliminating the 'remember to remove it' step entirely by making the automated scanner interrupt the commit before it completes.

How do I install Gitleaks as a pre-commit hook for all developers?

Use the pre-commit framework to manage Gitleaks as a standardized hook. Step 1: Create a .pre-commit-config.yaml in the repository root: repos:\n- repo: https://github.com/gitleaks/gitleaks\n rev: v8.18.4\n hooks:\n - id: gitleaks. Step 2: Add to the repository and commit the config file. Step 3: Developers run pre-commit install once per clone to register the hook. After installation, git commit automatically runs Gitleaks on staged files before the commit completes. To enforce this across all repositories in your organization, include pre-commit install in your developer onboarding scripts and CI pipeline (run pre-commit run --all-files on every pull request to catch any developer who skipped local hook installation).

What is GitHub push protection and how does it differ from pre-commit hooks?

GitHub push protection is a server-side control that blocks git push operations when the pushed commits contain secrets matching GitHub's secret pattern database. It intercepts at the remote repository level, so it catches secrets even from developers who have not installed local pre-commit hooks. It covers 200+ secret patterns including tokens from over 100 service providers. Limitation: it only blocks pushes to GitHub (not local commits or pushes to other remotes), and developers can bypass it with a documented reason. Pre-commit hooks are client-side (local) and fire before the commit, providing earlier feedback. The correct architecture uses both: pre-commit hooks for immediate local feedback and push protection as a server-side backstop.

How do I scan my existing repository history for secrets that have already been committed?

Run Gitleaks in detect mode against the full git history: gitleaks detect --source /path/to/repo --report-format json --report-path gitleaks-report.json. This scans all commits in the repository history, not just the current working tree. For a large repository, add --log-opts '--since=1 year ago' to limit the history depth. Review the report JSON for confirmed secrets, noting the commit hash, file path, and line number for each finding. All detected credentials must be treated as compromised: revoke or rotate them immediately, regardless of whether the commit is removed from history. Also scan all branches and tags: gitleaks detect --source . --log-opts '--all'.

If a secret is already in git history, does removing it with git rebase or filter-branch fix the problem?

No. The moment a secret is pushed to a remote repository (especially a public one), it must be treated as fully compromised regardless of subsequent history rewriting. GitHub, GitLab, and Bitbucket cache repository data, and web crawlers, automated secret scanners, and bots index public repositories continuously. A secret that was public for even 5 minutes has likely been captured by automated scanning infrastructure. Rewriting history removes the secret from new clones but does not affect existing clones, forks, cached copies, or external captures. The mandatory response is: revoke/rotate the credential immediately, rewrite history to prevent future exposures from clones, and then investigate whether the credential was used.

How do I handle a developer who bypasses pre-commit hooks using git commit --no-verify?

Pre-commit hooks can be bypassed with --no-verify, which is why the server-side push protection layer is critical. For GitHub: enable push protection at the organization level (GitHub > Organization Settings > Code security > Push protection) so bypass requires an audited documented reason. For GitLab: enable Secret Detection in the CI pipeline as a required job that blocks merges when secrets are detected. For both: configure branch protection rules that require CI status checks to pass before merges, so a developer who bypasses local hooks still cannot merge without passing the server-side scan. Make bypass a documented, audited event that generates a security ticket for review, not a frictionless option.

How do I rotate AWS credentials that were accidentally committed to a public repository?

Assume the credentials were compromised the moment they were pushed. Immediate rotation sequence: Step 1: Deactivate the access key immediately in AWS IAM (do not wait to investigate impact first — every minute of active key = potential unauthorized use). Step 2: Create new access key and update all systems that use it. Step 3: Check CloudTrail for any API calls made with the compromised key after the commit timestamp: aws cloudtrail lookup-events --lookup-attributes AttributeKey=AccessKeyId,AttributeValue=AKIAIOSFODNN7EXAMPLE --start-time [commit time] --end-time [now]. Step 4: Review CloudTrail for unauthorized actions (resource creation in unusual regions, IAM privilege escalation, S3 data access). Step 5: Delete the old access key after all systems are updated.

Sources & references

  1. Gitleaks: Secret Detection Tool
  2. GitHub Push Protection Docs
  3. Yelp detect-secrets
  4. pre-commit framework

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.