Stop Committing Secrets: A Practical Guide to Pre-Commit Secret Detection
You’ve done it. Or someone on your team has. A .env file slips into a commit, an AWS key ends up in a config file, a GitLab token gets hardcoded in a script “just for testing”. A few minutes later it’s in your git history — forever.
This post covers the current best practice for catching secrets before they ever touch your repository, without drowning your team in false positives.
Why Not Just One Tool?
Two open-source tools dominate the secret scanning space: Gitleaks and TruffleHog. They are often compared as if you have to pick one. You don’t — and you shouldn’t.
They solve different problems:
| Gitleaks | TruffleHog | |
|---|---|---|
| Approach | Regex pattern matching | Regex + entropy analysis |
| Speed | Sub-second (no network calls) | Slower (makes API calls) |
| Secret types | 150+ built-in rules | 800+ detectors |
| Credential verification | ✗ | ✓ (checks if the key is still active) |
| Best fit | Pre-commit hook | CI/CD pipeline |
TruffleHog’s killer feature is credential verification: when it finds what looks like an AWS access key, it actually calls the AWS API to check whether that key is valid and what permissions it has. This turns 200 noisy findings into 15 confirmed active credentials that need immediate rotation.
But that verification requires network calls — making it too slow for a blocking pre-commit hook where developers expect feedback in under a second.
The right architecture:
git commit → Gitleaks (fast, blocking) → commit accepted or rejected
git push → TruffleHog in CI (deep, verified) → MR blocked if active secret found
The Recommended Setup
1. Pre-commit: Gitleaks (native binary)
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.3 # pin to latest stable
hooks:
- id: gitleaks
That’s it. The gitleaks hook (not gitleaks-docker) uses the locally installed binary, scans only staged changes, and blocks the commit in milliseconds if a secret is found.
Install it once:
pip install pre-commit
pre-commit install
2. CI/CD: TruffleHog (verified secrets only)
For GitLab CI, add a dedicated job that runs on merge requests and only reports verified, active credentials:
# .gitlab-ci.yml
secret-scan:
stage: test
image: trufflesecurity/trufflehog:latest
script:
- trufflehog git file://. \
--since-commit $CI_MERGE_REQUEST_DIFF_BASE_SHA \
--only-verified \
--fail
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
The --only-verified flag is critical: without it, TruffleHog reports everything that looks like a secret. With it, you only see credentials that are confirmed live — zero ambiguity, zero alert fatigue.
Handling False Positives
The most common pushback against secret scanning is false positives: test passwords, example API keys in docs, fixture files. Gitleaks gives you three ways to deal with this cleanly.
Option 1: Inline annotation
Mark a specific line as intentionally allowed:
# This is a test credential — not a real key
TEST_API_KEY = "AKIAIOSFODNN7EXAMPLE" # gitleaks:allow
Option 2: .gitleaksignore
Gitleaks generates a fingerprint for every finding. You can commit known false positives to a .gitleaksignore file:
# Generate fingerprints for current findings
gitleaks detect --report-format json -r report.json
cat report.json | jq -r '.[].Fingerprint'
# .gitleaksignore
abc123def456:tests/fixtures/auth_test.go:generic-api-key:42
Option 3: Custom rules in .gitleaks.toml
For broader exclusions — test directories, example files, known-safe patterns:
[extend]
useDefault = true # keep all built-in rules
[[allowlists]]
description = "Test fixtures and documentation examples"
paths = [
'''tests/fixtures/.*''',
'''docs/examples/.*''',
]
regexes = [
# Ignore anything that looks like an intentional test credential
'''(?i)(test|fake|example|dummy|placeholder)[-_]?(password|key|token|secret)''',
]
Tip: Review your
.gitleaksignorefile monthly. It’s easy for a real secret to be accidentally allowlisted alongside the false positives.
Going Further: Custom Detectors
Both tools support custom rules for internal secret formats — proprietary API keys, internal service tokens, etc.
In Gitleaks, a custom rule looks like this:
[[rules]]
id = "internal-service-token"
description = "Internal service token"
regex = '''myapp_[a-zA-Z0-9]{32}'''
keywords = ["myapp_"]
TruffleHog supports custom detectors via Go plugins, but for most teams the TOML approach in Gitleaks is sufficient for custom internal formats.
Summary
- Pre-commit → Gitleaks (native binary, fast, blocking)
- CI/CD → TruffleHog with
--only-verified(confirms the secret is actually active) - Manage false positives with
# gitleaks:allow,.gitleaksignore, or.gitleaks.tomlallowlists - Add custom regex rules for any internal token formats your team uses
The goal isn’t zero false positives — it’s making secret detection low-friction enough that developers don’t disable it. A fast pre-commit hook that occasionally asks you to annotate a test credential is far better than no hook at all.