GitHub's automated secret scanning found 12.8 million tokens exposed in public repositories in 2023 alone. Those were only the repos GitHub could scan. Your private repo, your team's fork, a contractor's clone: none of those show up in that count.
The good news: two free tools will scan your entire git history in under two minutes, and you can block future leaks with a one-time hook setup.
TL;DR
Run gitleaks git --source . to scan your full commit history for secrets. Add a pre-commit hook with gitleaks to block secrets before they're committed. Add a CI step to catch anything that slips past the hook. If you find a secret in history, rotate it immediately. Deleting the file does nothing: the commit history still has it.
Two tools worth knowing
Gitleaks is the faster, more widely used option. It detects 150+ secret types (Stripe keys, AWS credentials, GitHub tokens, OpenAI keys, and more), scans staged files and full history, and exits with a non-zero code when secrets are found (which makes it easy to fail CI).
detect-secrets from IBM takes a different approach: it creates a baseline file listing known secrets so you can audit them, rather than blocking on every match. Useful when you have a codebase with intentional test credentials or documentation examples.
This guide focuses on gitleaks for the pre-commit and CI cases, and shows detect-secrets for baseline auditing.
Step 1: Scan your working tree
Install gitleaks
# macOS
brew install gitleaks
# Linux / Windows
# Download from https://github.com/gitleaks/gitleaks/releases
# or: go install github.com/zricethezav/gitleaks/v8@latest
Scan staged and tracked files
# Scan the current working directory (tracked files only)
gitleaks detect --source .
# Scan only files staged for the next commit
gitleaks detect --staged
This checks the files as they exist right now. It does not check history.
Step 2: Scan commit history
Scanning current files is not enough. A secret committed six months ago and then deleted is still in every commit between then and now. Anyone who clones your repo can find it.
Walk the full git history
# Scan every commit reachable from the current HEAD
gitleaks git --source .
# Save the report as JSON (useful for CI and auditing)
gitleaks git --source . --report-path gitleaks-report.json
The output lists the secret type, the file, the commit SHA, and the first few characters of the value. If you see hits, rotate those credentials before doing anything else.
Rotating comes first. Cleaning history (via BFG or git filter-repo) removes the secret from future clones, but anyone who already cloned the repo has the secret. Rotate first, clean second.
Step 3: Block secrets before they're committed
A one-time history scan catches the past. A pre-commit hook catches the future.
Set up the pre-commit framework
pip install pre-commit
Create .pre-commit-config.yaml in your repo root:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.24.0
hooks:
- id: gitleaks
Then install the hook:
pre-commit install
From now on, every git commit runs gitleaks against the staged diff. If a secret matches, the commit is blocked and the output tells you what was found.
Pin the rev value to a specific tag (v8.24.0), not latest or main. This avoids surprise breakage when gitleaks releases a new version with stricter rules.
Test it works
Create a file with a fake secret pattern and try to commit it:
echo "STRIPE_SECRET_KEY=sk_live_faketest1234567890abcdef" > /tmp/test-secret.txt
git add /tmp/test-secret.txt
git commit -m "test"
Gitleaks should block the commit and print:
Finding: STRIPE_SECRET_KEY=sk_live_fake...
Secret: sk_live_fake...
RuleID: stripe-api-key
Entropy: 3.58
File: test-secret.txt
Delete the test file before continuing.
Step 4: Add detect-secrets for a baseline audit
When you inherit a codebase, you may not know what secrets are intentional (test credentials, documentation snippets) and what are real. detect-secrets handles this with a baseline workflow.
Create a baseline
pip install detect-secrets
# Scan and write a baseline of all current "secrets" (real and false positives)
detect-secrets scan > .secrets.baseline
Open .secrets.baseline in your editor. For each entry, decide: is this a real secret or a false positive? Mark false positives as audited:
detect-secrets audit .secrets.baseline
Commit the baseline file. Future scans flag only new secrets not in the baseline.
detect-secrets works alongside gitleaks, not instead of it. Use detect-secrets for the initial audit of an existing codebase, gitleaks for the ongoing hook and CI enforcement.
Step 5: Add CI/CD scanning
Pre-commit hooks run locally. CI catches anything that bypassed the hook (a commit from GitHub's web UI, a squash-merge, a team member without the hook installed).
GitHub Actions workflow
Add this to .github/workflows/secret-scan.yml:
name: Secret Scan
on: [push, pull_request]
jobs:
gitleaks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history for history scan
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
fetch-depth: 0 is required. Without it, Actions does a shallow clone and gitleaks only sees the tip commit, missing everything in history.
What gitleaks detects
Gitleaks ships with rules for 150+ secret types. A few common ones relevant to vibe-coded apps:
| Service | Pattern matched |
|---|---|
| Stripe | sk_live_..., rk_live_..., sk_test_... |
| OpenAI | sk-... (51 chars) |
| AWS | AKIA... access key IDs |
| GitHub | ghp_..., ghs_..., ghr_... tokens |
| Supabase | service role keys and JWT secrets |
| Generic | High-entropy strings assigned to API_KEY, SECRET, PASSWORD, TOKEN variable names |
The generic high-entropy rule catches a lot of custom secrets that don't match a specific service pattern. You may get false positives on base64-encoded content. Add those paths to a .gitleaksignore file.
Create .gitleaksignore in your repo root and add paths or commit SHAs for known false positives. Format is one path or {sha}:{filepath} per line.
What to do when you find a secret
- Rotate the credential in the service dashboard (Stripe, OpenAI, AWS, etc.) before anything else
- Check access logs for the service to see if the key was used by anyone other than you
- Clean git history if the repo is or was ever public (BFG Repo Cleaner is the fastest path)
- Force-push the cleaned history and have all team members re-clone
- Add the secret pattern to gitleaks config or
.gitleaksignoreso future scans don't report the now-rotated key
What is the best tool to detect secrets in git?
Gitleaks is the most widely used open-source tool for detecting secrets in git repos. It scans both the working tree and commit history, detects 150+ secret types, and is easy to wire into pre-commit hooks and CI/CD. detect-secrets from IBM is a good alternative with a baseline workflow that reduces false positives.
How do I scan git history for secrets?
Run gitleaks git --source . to walk every commit in the current repository. This scans the full history, not just the current files. If secrets are found in old commits, rotate the credentials immediately. They are already public, even if the file has since been deleted.
Does deleting a file remove secrets from git?
No. Deleting a file only removes it from the current commit. The secret still exists in every previous commit where the file was present. Anyone who clones your repo can check out the old commit and read the secret. Use BFG Repo Cleaner or git filter-repo to rewrite history after rotating credentials.
How do I prevent secrets from being committed in the first place?
Use a pre-commit hook that runs gitleaks before each commit. The hook blocks the commit if a secret is detected. Wire it via the pre-commit framework so all teammates get it automatically when they run pip install pre-commit && pre-commit install.
Will secret scanning catch secrets in .env files?
Only if the .env file is tracked by git. If it is in .gitignore, scanners won't see it because git doesn't track it. The risk is when .env files are accidentally committed. The scanner catches that. Keep .env in .gitignore and commit only .env.example with placeholder values.
Secrets in your deployed app?
Scanning your git repo catches what's in version control. CheckYourVibe scans your live app for API keys exposed in JavaScript, misconfigured headers, and other runtime security issues.