Skip to content

Lesson 5 of 9 · 20m

Secrets Scanning: Committed Keys Are Forever

Deleting the line doesn't delete the secret — git is append-only, bots watch every public commit, and the only real remediation is rotation.

Years ago I watched an engineer commit an AWS access key "just to test something," delete it four minutes later, and open an incident anyway — because the key was already in git history, already in every clone and CI cache, and the bots that watch public commits don't sleep. We spent a weekend rotating keys and reading CloudTrail. The deletion commit changed nothing. That's the lesson.

Deleting the line doesn't delete the secret

Git is append-only. A credential in any commit survives in every clone, every fork, every checkout cache, every mirror. Once a real credential touches a repo — private or public — the only remediation is rotate it, then scrub history if you must. git filter-repo and BFG help with copies you control; they do nothing for copies you don't.

Two scanners, two jobs

# gitleaks: regex + entropy scanning of history and the working tree
gitleaks detect --source . --redact --exit-code 1   # CI / audit mode
gitleaks protect --staged --verbose                  # pre-commit mode

# trufflehog: similar coverage, plus live verification
trufflehog git file://. --only-verified

--only-verified is the flag that matters: trufflehog actually tries each candidate credential against its provider and reports only the ones that work. That separates a live AWS key — page someone now — from a thousand test fixtures and documentation examples that belong on a cleanup queue.

Coverage has to exist in three places

  • Pre-commit — gitleaks protect --staged via .pre-commit-config.yaml catches the secret before history exists.
  • CI — gitleaks detect with fetch-depth: 0 catches what bypassed the hooks, and hooks do get bypassed.
  • Platform — GitHub push protection, GitLab secret detection. A third net for what both of yours miss.
# .pre-commit-config.yaml
- repo: https://github.com/gitleaks/gitleaks
  rev: v8.18.4
  hooks:
    - id: gitleaks

When it fires

Rotate first, investigate after. Write the playbook per secret type before you need it: revoke at the provider, check provider-side audit logs for use (aws iam get-access-key-last-used, CloudTrail for calls made with the key), commit the removal, then baseline the historical string in the scanner so it stops re-firing.

Field notes

  • Entropy detection finds high-entropy strings that regex misses — and flags every minified JS blob in the repo. Tune it or drown.
  • # gitleaks:allow inline markers exist for fixtures and test vectors — require a comment explaining why, and review them.
  • The real fix is upstream: if developers have a secrets manager (Doppler, Vault, cloud-native) they actually like, they stop pasting keys into .env in the first place.