Secret Scanning and Pre-Commit Guardrails

SecurityDevSecOpsGit

The most expensive line of code I've seen a junior engineer write was a hard-coded AWS key committed to a repo that briefly went public. The key was live for under an hour and it still cost real money and a very long incident review. Secret scanning exists to make that story impossible: it's automated detection of credentials — API keys, tokens, private keys, passwords — that have leaked into source code, and when you wire it into a pre-commit hook, the leak gets stopped on the developer's laptop before it ever becomes part of git history.

The key insight that shapes everything below: git history is forever. Once a secret is committed and pushed, deleting it in a later commit does nothing — it still sits in the history and in every clone. So the entire strategy is about layers of defense, with the cheapest, earliest layer catching the most.

Why pre-commit is the highest-leverage layer

A pre-commit hook runs before the commit object is even created. If the scanner finds a secret in your staged diff, the commit is rejected, and the secret never enters history. Nothing to rotate, nothing to scrub, no incident. That's a fundamentally better position than any downstream check, because downstream means the secret already exists somewhere it shouldn't.

Contrast the cost curve. Catch a secret at pre-commit: 30 seconds of the developer fixing their change. Catch it in CI after a push: rotate the credential, rewrite history, force-push, notify the team. Catch it after it's exploited: a full security incident. The economics make pre-commit the obvious place to invest first.

The catch is that pre-commit hooks live on developer machines and can be bypassed (git commit --no-verify). So pre-commit is necessary but not sufficient — you need a server-side backstop too. More on that below.

Setting up gitleaks with pre-commit

gitleaks is my default — fast, single binary, sane rules. The cleanest way to wire it in is through the pre-commit framework, which manages the hook lifecycle:

# .pre-commit-config.yaml
repos:
  - repo: https://github.com/gitleaks/gitleaks
    rev: v8.18.0
    hooks:
      - id: gitleaks

Then each developer runs pre-commit install once, and gitleaks scans staged changes on every commit. You can also run it standalone against the full history to find secrets already lurking:

# Scan the entire git history, not just staged changes
gitleaks detect --source . --verbose

# Scan only what's staged (what the pre-commit hook effectively does)
gitleaks protect --staged --verbose

The distinction matters: detect walks history (use it once when onboarding a repo to find existing leaks), protect --staged checks the pending commit (what the hook uses day to day).

Two detection strategies, both imperfect

Scanners find secrets two ways, and knowing the difference explains their failure modes:

trufflehog leans harder into verification — it can actually test whether a found credential is live by calling the relevant API, which slashes false positives because it distinguishes "looks like a secret" from "is an active secret." That verification step is genuinely useful; a verified-live AWS key is a five-alarm fire, while an expired test key is noise.

Tool Strength Best used for
gitleaks Fast, easy pre-commit integration The commit-time gate on every machine
trufflehog Live credential verification CI scans, prioritizing real leaks

I run both: gitleaks at pre-commit for speed, trufflehog in CI for verified findings. They complement rather than compete.

The false-positive problem is the real enemy

Here's the honest failure mode, and it's not technical — it's human. If your scanner cries wolf on every base64 test fixture and example key in the docs, developers start reflexively adding --no-verify or blanket-allowlisting, and then the tool catches nothing. A noisy secret scanner is worse than none, because it teaches people to ignore it. This is the same alert-fatigue dynamic that guts vulnerability programs, discussed in DevSecOps and shifting security left.

Managing false positives is therefore a first-class task, not an afterthought:

Tune it until a scanner alert almost always means a real problem. That's what keeps developers trusting the gate.

The server-side backstop

Because pre-commit is bypassable, you need a check that isn't. Run secret scanning in CI on every push and pull request, and — ideally — enable push protection at the platform level (GitHub, GitLab, and others offer server-side secret scanning that rejects pushes containing recognized secrets). The layers reinforce each other:

  1. Pre-commit — catches most leaks for free, on the developer's machine.
  2. Pre-push / CI — catches what bypassed pre-commit, blocks the merge.
  3. Platform push protection — catches recognized secrets even if CI is misconfigured.
  4. Continuous history scanning — periodically re-scans for secrets and newly-added detection rules.

No single layer is trustworthy alone; together they make a leak genuinely hard.

When a secret leaks anyway — and preventing the next one

When (not if) a secret slips through, the order of operations is non-negotiable: rotate first. The moment a credential lands in a shared repo, treat it as compromised — attackers scan public git constantly, and even private repos have too many eyes. Revoke and reissue the credential, then investigate usage, then consider rewriting history to purge it. Rewriting history without rotating is security theater; the leaked value is already out.

The durable fix is to stop putting secrets in code at all. Secret scanning is a safety net, not a strategy — the strategy is proper secrets management with a vault and injected runtime credentials, so there's nothing to hard-code in the first place. Scanning catches the mistakes; a real secrets architecture removes the opportunity to make them. Run both, and the class of "credential in git" incidents mostly disappears from your life — which, having lived through the alternative, is exactly where you want to be.

Resources

Frequently asked questions

What is secret scanning?

Secret scanning is the automated detection of credentials — API keys, tokens, private keys, database passwords — that have been committed into source code or other files. Tools like gitleaks and trufflehog match patterns and entropy signatures against your code and git history, flagging anything that looks like a live secret. The goal is to catch leaked credentials before they reach a shared repository, or to find ones that already have.

Why use pre-commit hooks for secret detection?

A pre-commit hook runs the scanner on your staged changes before the commit is created, so a secret is blocked on the developer's machine and never enters git history at all. That's dramatically cheaper than catching it after the fact — once a secret is committed and pushed, it's in the history forever and must be treated as compromised. Pre-commit is the earliest and cheapest place to stop a leak.

What should I do if a secret has already been committed?

Rotate the credential first — assume it's compromised the moment it hit a shared repository, because scrubbing history doesn't un-leak it. Then revoke the old value, investigate whether it was used, and only after rotation consider rewriting history to remove the secret. Deleting the secret from the latest commit is not enough; it lives in the git history and any clone until the history is rewritten and force-pushed.

Hiring a senior Android / Flutter engineer?

I architect and ship production mobile software — Kotlin, Jetpack Compose, Flutter — for robotics, EV infrastructure, fintech, and real-time systems. Open to remote roles in Europe and the US.

Get in touch →