Security incidents often start with a simple mistake.
A developer accidentally commits an API key, a database password, a private key, or a cloud credential. The secret gets pushed to a repository, appears in CI logs, gets replicated to forks, or becomes part of Git history.
Even when detected quickly, the cleanup can be painful:
- Rotate credentials
- Clean repository history
- Investigate exposure
- Update environments
- Document the incident
A much better approach is to prevent secrets from being committed in the first place.
In this article, we'll configure Betterleaks and Git pre-commit hooks to automatically scan staged changes and block commits containing sensitive information.
Why Local Secret Scanning Matters
Many organizations rely on:
- GitHub Secret Scanning
- GitLab Secret Detection
- CI/CD security pipelines
- Repository scanning solutions
While these tools are important, they usually identify problems after the secret has already entered the repository.
Local scanning shifts detection further left.
Benefits
✅ Immediate feedback for developers
✅ Secrets never reach Git history
✅ Faster remediation
✅ Consistent security practices
✅ Reduced risk of accidental exposure
The earlier a problem is discovered, the cheaper it is to fix.
Step 1: Install Betterleaks
Install Betterleaks on your workstation.
macOS
brew install betterleaks
Linux
BETTERLEAKS_VERSION="1.8.1"
curl -sSfL \
"https://github.com/betterleaks/betterleaks/releases/download/v${BETTERLEAKS_VERSION}/betterleaks_${BETTERLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz
sudo mv betterleaks /usr/local/bin/
Verify Installation
betterleaks --version
If a version number is displayed, you're ready to continue.
Step 2: Store Git Hooks Inside the Repository
By default, Git stores hooks inside:
.git/hooks
The problem is that this directory is not version-controlled.
To ensure every developer uses the same hooks, create a dedicated directory:
my-project/
├── .githooks/
│ └── pre-commit
├── .betterleaks.toml
└── README.md
Configure Git to use this directory:
git config core.hooksPath .githooks
This command only needs to be executed once per cloned repository.
Step 3: Create the Pre-Commit Hook
Create the file:
.githooks/pre-commit
Add the following content:
#!/usr/bin/env bash
# Verify Betterleaks is installed
if ! command -v betterleaks &> /dev/null; then
echo "Betterleaks is not installed."
echo "Please install Betterleaks before committing."
exit 1
fi
echo "Running Betterleaks scan..."
betterleaks git . \
--staged \
--config .betterleaks.toml
EXIT_CODE=$?
if [ $EXIT_CODE -ne 0 ]; then
echo ""
echo "Secrets detected."
echo "Commit blocked."
exit 1
fi
echo "No secrets found."
exit 0
Make the hook executable:
chmod +x .githooks/pre-commit
Now every commit will automatically trigger a secret scan.
Step 4: Configure Betterleaks
Create the configuration file:
.betterleaks.toml
Example configuration:
title = "Project-Wide Betterleaks Configuration"
[extend]
useDefault = true
# Optional:
# disabledRules = ["generic-api-key"]
[[rules]]
id = "custom-api-token"
description = "Detect custom API tokens"
regex = '''MYTOKEN_[A-Za-z0-9]{32}'''
keywords = [
"MYTOKEN_"
]
[allowlist]
description = "Ignore test fixtures"
paths = [
'''(^|/)testdata/''',
'''(^|/)fixtures/''',
'''(^|/)__mocks__/'''
]
stopwords = [
"dummy_token",
"example_secret",
"test_api_key"
]
This configuration:
- Uses Betterleaks' built-in detection rules
- Adds project-specific secret detection
- Reduces false positives
- Excludes known test data
Step 5: Test the Setup
Let's verify that the hook works.
Create a file containing a fake GitHub token:
echo "token: ghp_AbC123xYz456D7Ef89GhI01JkLmNoPqRsTuV" > test-secret.txt
Stage the file:
git add test-secret.txt
Attempt a commit:
git commit -m "testing secret detection"
Expected output:
Running Betterleaks scan...
Secrets detected.
Commit blocked.
The commit will fail, preventing the secret from entering the repository.
Handling False Positives
No detection tool is perfect.
If Betterleaks incorrectly identifies a value as a secret, you have several options.
Allow a Specific Line
example_value # betterleaks:allow
Ignore Files or Directories
Create a:
.betterleaksignore
file to define exclusions.
Customize Detection Rules
Adjust your rules inside:
.betterleaks.toml
The goal is to reduce noise while maintaining effective security controls.
Emergency Bypass
In rare situations, you may need to bypass the hook:
git commit --no-verify -m "emergency commit"
Use this carefully.
If developers regularly bypass security controls, it may indicate that the rules need refinement.
Onboarding New Developers
A simple developer onboarding process might look like:
git clone <repository>
cd <repository>
brew install betterleaks
git config core.hooksPath .githooks
That's it.
From that point onward, every commit is automatically checked before it is created.
Defense in Depth: Local + CI/CD Scanning
Local secret scanning should complement, not replace, existing security controls.
A layered approach typically includes:
| Layer | Purpose |
|---|---|
| Pre-Commit Hooks | Immediate developer feedback |
| Pull Request Validation | Team-level review |
| CI/CD Scanning | Automated enforcement |
| Repository Scanning | Continuous monitoring |
| Secret Management Solutions | Secure storage and rotation |
Security controls are most effective when they work together.
Conclusion
Secret leaks are among the easiest security incidents to prevent.
By integrating Betterleaks directly into the local development workflow, teams can catch sensitive information before it enters a repository.
The setup is lightweight, easy to maintain, and provides immediate value without disrupting developers.
If you're already scanning repositories and CI/CD pipelines for secrets, consider adding local detection as another layer of defense.
The best secret leak is the one that never reaches Git in the first place.
Have You Tried Local Secret Scanning?
How does your team prevent accidental secret exposure?
Do you rely on GitHub Secret Scanning, GitLab Secret Detection, gitleaks, Betterleaks, custom tooling, or a combination of approaches?
I'd be interested to hear what has worked best in your environment.
Top comments (0)