Block Secrets Before They Hit GitHub with Gitleaks
Set up a Gitleaks pre-commit hook and GitHub Actions check that reject commits containing credentials.
What you'll build
A two-layer defense against leaked credentials: a Gitleaks pre-commit hook that blocks any local commit containing API keys or tokens, plus a matching GitHub Actions check that catches anything that slips past (or around) the hook.
Prerequisites
- Gitleaks v8.30.1 (current stable; anything ≥ v8.19 works, since that's where the modern
gitsubcommand landed) - pre-commit 4.6.x — the pre-commit framework manages the hook so teammates get it with one command
- Git and a GitHub repository you can push to
- macOS or Linux with Homebrew, or grab binaries from the Gitleaks releases page and install pre-commit with
pip install pre-commit
You don't strictly need Gitleaks installed locally — the pre-commit framework builds it for you — but having the CLI around is useful for one-off scans.
Step 1: Install the tools
brew install gitleaks pre-commit
Confirm both are on your PATH:
gitleaks version # 8.30.1
pre-commit --version # pre-commit 4.6.1
Step 2: Add the pre-commit hook
In your repo root, create .pre-commit-config.yaml:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks
Then install the hook into .git/hooks:
pre-commit install
Under the hood this hook runs gitleaks git --pre-commit --redact --staged --verbose — it scans only your staged changes (fast) and redacts the secret in its output so the leak doesn't also end up in your terminal scrollback or CI logs.
The first commit after installing is slow: pre-commit downloads a Go toolchain and compiles Gitleaks into a cached environment. Every run after that is near-instant. If you'd rather use your Homebrew-installed binary, swap id: gitleaks for id: gitleaks-system.
Commit the config so the hook ships with the repo — teammates just run pre-commit install once after cloning:
git add .pre-commit-config.yaml
git commit -m "Add gitleaks pre-commit hook"
Step 3: Add the CI check
Local hooks are advisory — anyone can skip them with git commit --no-verify. The CI check is the enforcement layer. Create .github/workflows/gitleaks.yml:
name: gitleaks
on:
pull_request:
push:
workflow_dispatch:
jobs:
scan:
name: gitleaks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_LICENSE: ${{ secrets.GITLEAKS_LICENSE }}
fetch-depth: 0 matters — gitleaks-action scans commit history, not just the checkout, so it needs more than a shallow clone. GITHUB_TOKEN is provided automatically and lets the action comment on PRs that contain leaks. GITLEAKS_LICENSE is only required for organization-owned repos (see Troubleshooting); on a personal repo the line is harmless.
Push, then make this check required under Settings → Branches → Branch protection rules so a red scan actually blocks merges.
Verify it works
Stage a fake AWS access key and try to commit it:
echo 'AWS_ACCESS_KEY_ID=AKIAQ4KMGN37VPXHW2TR' > .env
git add .env
git commit -m "add config"
The commit should be rejected with output like:
Detect hardcoded secrets using Gitleaks..................................Failed
- hook id: gitleaks
- exit code: 1
Finding: AWS_ACCESS_KEY_ID=REDACTED
Secret: REDACTED
RuleID: aws-access-token
Entropy: 4.121928
File: .env
Line: 1
Fingerprint: .env:aws-access-token:1
1:04PM INF scanning staged changes
1:04PM WRN leaks found: 1
Exit code 1 means leaks were found; 0 means clean. Clean up with git rm --cached .env && rm .env, then push a branch containing the same fake key to confirm the Actions run goes red too.
Troubleshooting
My test secret isn't detected. If you tested with AWS's documentation key AKIAIOSFODNN7EXAMPLE, that's expected: the aws-access-token rule has a built-in allowlist regex .+EXAMPLE$, so anything ending in EXAMPLE is deliberately ignored. Use a key like the one above.
🛑 missing gitleaks license in the Actions log. Organization-owned repos need a license for gitleaks-action (free and paid tiers exist — request one at gitleaks.io), stored as an encrypted repo secret named GITLEAKS_LICENSE. Personal accounts don't need one.
A real config value keeps getting flagged. For a known-fake secret, append a # gitleaks:allow comment to that line. Otherwise copy the Fingerprint: value from the finding into a .gitleaksignore file in the repo root — that suppresses exactly that occurrence, not the whole rule.
The hook passed but CI failed. The pre-commit hook only scans staged changes, so secrets committed before you installed it are invisible to it — CI scans full history and finds them. Audit locally with gitleaks git -v, and treat anything it finds as compromised: rotate the credential first, then clean up the history.
Next steps
- Add a custom
.gitleaks.tomlwith rules for your internal token formats, using[extend] useDefault = trueto keep the ~180 built-in rules. - Generate a baseline (
gitleaks git --report-path baseline.json, then--baseline-path baseline.jsonon later runs) to adopt Gitleaks on a legacy repo without drowning in old findings. - Scan non-git surfaces:
gitleaks dirfor arbitrary directories,gitleaks stdinfor piped input — handy for checking.envfiles and CI artifacts that never touch git.
Sources & further reading
- Gitleaks README — github.com
- Gitleaks pre-commit hook definitions — github.com
- Gitleaks-Action README — github.com
- pre-commit framework documentation — pre-commit.com
- Gitleaks default ruleset (gitleaks.toml) — github.com
Emeka has spent over a decade tracking threat actors, vulnerability disclosures, and the evolving landscape of application security, bringing a sharp continent-spanning perspective to his reporting. He's known for translating dense CVE advisories into clear, actionable context that developers and security teams alike actually read.
Discussion 0
No comments yet
Be the first to weigh in.