Intermediate

How to Set Up Pre-Commit Secret Scanning

Cleaning secrets out of git history is painful, slow, and involves force-pushing, which nobody enjoys. Preventing them from entering history in the first place takes about forty minutes, once, and then works forever. That is the trade this tutorial makes.

You will set up the pre-commit framework with gitleaks so that every commit is scanned for credentials before it is created. A secret that never reaches a commit never needs rotating, purging, or an awkward incident channel. If you have not yet checked whether your history is already contaminated, do a full repo scan first; hooks protect the future, not the past. The background reading is exposed secrets in your repo.

What you’ll need

  • A git repository you can commit to
  • Python and pip (the pre-commit framework is a Python tool, but it manages hooks for any language)
  • About 40 minutes

1. Install the pre-commit framework

You could write a raw shell script in .git/hooks/pre-commit, but raw hooks are not versioned, not shared with your team, and silently absent on fresh clones. The pre-commit framework fixes all three: the hook definition lives in a committed YAML file, and each developer activates it once.

pip install pre-commit

# or on macOS
brew install pre-commit

pre-commit --version

The framework reads a config file from your repo root, installs the tools each hook needs into an isolated environment, and wires everything into git for you.

2. Add gitleaks as a hook

Create .pre-commit-config.yaml in the repository root:

repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.4
    hooks:
      - id: gitleaks

Two things worth understanding here. The rev pins an exact gitleaks release, so every developer runs the same rules and an upstream change cannot surprise you. And the published gitleaks hook runs in protect mode against staged changes, which is exactly what you want at commit time: fast, scoped to the diff, no full-history walk.

Now activate it in your clone:

pre-commit install

This writes the shim into .git/hooks/pre-commit. From now on, git commit runs gitleaks first and refuses to proceed if it finds a secret in the staged changes.

3. Test the hook with a fake secret

Never trust a safety system you have not seen fail. Stage a fake credential and try to commit it:

echo 'aws_access_key_id = "AKIAIOSFODNN7EXAMPLE"' > leak-test.txt
git add leak-test.txt
git commit -m "test: should be blocked"

The commit should fail with a gitleaks finding pointing at the AWS key pattern. If it goes through, the hook is not installed; run pre-commit install again and check that you are in the right repo.

Clean up:

git restore --staged leak-test.txt
rm leak-test.txt

You can also run the scan across the whole repository on demand, which is useful right after setup:

pre-commit run --all-files

4. Handle false positives with an allowlist

Within a week, someone will hit a false positive: a dummy key in a test fixture, a sample token in documentation, a high-entropy string that is just a hash. If the hook cries wolf, developers will start bypassing it, and a bypassed hook is worse than no hook because it creates false confidence.

Create a gitleaks.toml in the repo root that extends the default rules and allowlists the known-safe cases:

[extend]
useDefault = true

[allowlist]
description = "Known safe test fixtures and docs"
paths = [
  '''tests/fixtures/.*''',
  '''docs/examples\.md''',
]
regexes = [
  '''AKIAIOSFODNN7EXAMPLE''',
]

Gitleaks picks up gitleaks.toml from the repo root automatically. Keep the allowlist tight and reviewed: every entry is a hole in the net, so each one should be a path or a specific known-fake value, never a broad pattern like “anything in src”.

The escape hatch for a genuine emergency is git commit with the SKIP=gitleaks environment variable set, which skips only this hook and leaves an obvious trace in your shell history. Treat its use as a code review question, not a habit.

5. Roll it out to the whole team

The config file is only half the rollout; the other half is people. Commit the two files:

git add .pre-commit-config.yaml gitleaks.toml
git commit -m "chore: add pre-commit secret scanning"

Then handle the one-time activation on each machine. Add it to your README and onboarding doc:

pip install pre-commit && pre-commit install

If your project has a bootstrap script (make setup, npm run prepare, a devcontainer), put pre-commit install in it so new clones are protected without anyone remembering anything. Hooks that depend on memory fail exactly when someone is rushed, and rushed commits are where secrets leak. This kind of process hardening is a standard line item in our security review checklist template.

6. Add a CI backstop

Local hooks can be skipped, uninstalled, or missing on a brand new laptop. The backstop is running the same scan server-side, where nobody can forget it. For GitHub Actions, create .github/workflows/gitleaks.yml:

name: gitleaks
on: [push, 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 matters: it fetches full history so the scan covers every commit in the push, not just the tip. With the hook catching secrets at commit time and CI catching anything that slips past, a credential now has to defeat two independent layers to reach your default branch. That is the difference between “we ask people to be careful” and an actual control.

Get a second pair of eyes (free)

Pre-commit scanning closes one door. An auditor looks at the whole house: how secrets are stored and injected at runtime, whether tokens expire, what your CI logs leak, and everything in the free code audit checklist. Once your hooks are in place, get a free code audit and a Webisoft engineer will manually review your repository and send you a prioritized report of what to fix next. It is free, and it is a human reading your code, not another scanner.

FAQ

Frequently asked questions

Will the hook slow down every commit?

Not noticeably. The published gitleaks hook runs in protect mode against the staged changes only, which typically takes a fraction of a second. Only the on-demand scan of all files walks the whole repository, and you choose when to run that.

The hook blocked a fake key in a test fixture. What is the right fix?

Add a targeted allowlist entry to gitleaks.toml, either the specific fake value as a regex or the fixture path. Keep every entry narrow, because each one is a hole in the net, and a broad pattern quietly turns the scanner off for real secrets too.

Can a teammate just bypass the hook?

Yes. Hooks are local, skippable with the SKIP environment variable, and simply absent on any clone where pre-commit install never ran. That is exactly why the CI backstop exists: the server-side scan runs whether or not the local hook did.

Should I ever update the pinned gitleaks version?

Yes, periodically, because newer releases add detection rules for new credential formats. The pin exists so upgrades happen deliberately rather than by surprise, so bump the rev in the config on your own schedule and re-run the fake-secret test to confirm the hook still blocks commits.

A real secret got committed anyway. Now what?

Rotate it immediately, before anything else, because deleting it from the code does not revoke it. Then run a full-history scan to see what else is already in there, since hooks only protect commits made after they were installed. History rewriting comes last, after the credential is already dead.

Free · no strings shown later

Get your free code audit

A repo URL and two minutes of your time. A Webisoft engineer sends back written findings (security, tech debt, performance) with file-and-line specifics.

Request my free audit