Introduction
Leaked secrets are one of the most preventable — and most common — security incidents in software development. API keys, database passwords, cloud credentials, and private tokens end up committed to version control every day. Once pushed to a remote repository, they can be indexed by search engines, scraped by bots, or discovered by threat actors within minutes.
Gitleaks is a fast, open-source SAST (Static Application Security Testing) tool purpose-built for detecting secrets in git repositories. It scans both the working tree and the full commit history, meaning it catches secrets that were committed and later deleted — the file is gone, but the secret lives on in history.
In this guide you will:
- Install Gitleaks on Linux, macOS, and via Docker
- Scan a repository's current state and entire commit history
- Write a custom
.gitleaks.tomlconfig to tune detections - Add a pre-commit hook to block secrets before they reach the remote
- Integrate Gitleaks into a GitHub Actions CI/CD pipeline
Prerequisites
Before you begin, make sure you have:
- Git 2.28+ —
git --version - A local git repository with at least one commit
- curl or wget for downloading the binary
- Optional: Docker for containerized scans
- Optional: A GitHub repository and Actions access for the CI/CD section
Step 1 — Install Gitleaks
Linux (amd64)
# Download the latest release (check https://github.com/gitleaks/gitleaks/releases for current version)
GITLEAKS_VERSION="8.21.2"
curl -sSfL \
"https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
-o /tmp/gitleaks.tar.gz
tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
sudo mv /tmp/gitleaks /usr/local/bin/gitleaks
sudo chmod +x /usr/local/bin/gitleaks
# Verify
gitleaks versionmacOS (Homebrew)
brew install gitleaks
gitleaks versionDocker (no install required)
# Pull the image
docker pull ghcr.io/gitleaks/gitleaks:latest
# Alias for convenience (add to ~/.bashrc or ~/.zshrc)
alias gitleaks='docker run --rm -v "$(pwd)":/path ghcr.io/gitleaks/gitleaks:latest'Windows (Scoop or direct download)
# Via Scoop
scoop install gitleaks
# Or download the Windows zip from GitHub Releases and add to PATHStep 2 — Your First Scan
Navigate to any git repository and run a basic scan:
cd /path/to/your/repo
# Scan the current working directory (staged + unstaged files)
gitleaks detect --source . --verbose
# Scan the entire git commit history (catches deleted secrets)
gitleaks git --source . --verboseUnderstanding the output
A finding looks like this:
Finding: ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Secret: ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
RuleID: github-pat
Entropy: 3.58
File: config/settings.py
Line: 14
Commit: a3f2c1d9...
Author: Dev User <dev@example.com>
Date: 2025-11-03T10:22:41Z
Fingerprint: a3f2c1d9:config/settings.py:github-pat:14
Key fields:
- RuleID — which detection rule matched (e.g.,
github-pat,aws-access-key) - Entropy — Shannon entropy of the matched string (higher = more random = more likely a real secret)
- Fingerprint — unique ID used to allowlist false positives
Gitleaks ships with 150+ built-in rules covering AWS keys, GitHub tokens, Stripe keys, Slack webhooks, private keys, JWT secrets, and more.
Step 3 — Scan Modes and Useful Flags
# Scan only staged changes (useful in pre-commit hooks)
gitleaks detect --staged
# Output findings as JSON (for SIEM/reporting pipelines)
gitleaks git --source . --report-format json --report-path findings.json
# Redact secrets from output (safe for logs)
gitleaks git --source . --redact
# Scan a specific branch
gitleaks git --source . --log-opts="origin/main..HEAD"
# Exit with non-zero code on findings (useful in CI)
gitleaks git --source . --exit-code 1
# Verbose mode shows all files scanned, not just findings
gitleaks git --source . --verbose
# Scan only the last N commits
gitleaks git --source . --log-opts="-20"Step 4 — Configure Gitleaks with .gitleaks.toml
The default ruleset is comprehensive but you will likely need to tune it — either to add custom rules for your stack or to allowlist known false positives.
Create .gitleaks.toml in your repository root:
# .gitleaks.toml
title = "CosmicBytez Gitleaks Config"
[extend]
# Extend the default ruleset rather than replacing it
useDefault = true
# ──────────────────────────────────────────────
# Custom rules for internal secrets patterns
# ──────────────────────────────────────────────
[[rules]]
id = "internal-api-key"
description = "Internal API key with CBZ prefix"
regex = '''CBZ-[A-Za-z0-9]{32,}'''
tags = ["internal", "api-key"]
[rules.allowlist]
# Allow keys in test fixtures
paths = ['''tests/fixtures/.*''']
[[rules]]
id = "database-dsn"
description = "Database connection string with embedded credentials"
regex = '''(postgres|mysql|mongodb):\/\/[^:]+:[^@]+@'''
tags = ["database", "credentials"]
# ──────────────────────────────────────────────
# Global allowlist — suppress known false positives
# ──────────────────────────────────────────────
[allowlist]
description = "Global allowlist"
# Allowlist by file path (regex)
paths = [
'''\.gitleaks\.toml''',
'''package-lock\.json''',
'''yarn\.lock''',
'''.*\.test\.(ts|js)''',
'''docs/.*''',
]
# Allowlist specific commit SHAs (one-off exceptions)
commits = [
# "abc123def456..." # add commit SHAs here
]
# Allowlist by finding fingerprint (from the Fingerprint field in output)
# Use this to silence a specific known-safe detection without blanket ignoring the rule
regexes = [
# Example: test/mock data that looks like a secret
'''EXAMPLE_SECRET_DO_NOT_USE''',
'''test_secret_placeholder''',
]Apply the config:
gitleaks git --source . --config .gitleaks.toml --verboseStep 5 — Add a Pre-Commit Hook
A pre-commit hook runs locally before every commit, blocking secrets at the source rather than after a push.
Option A: Manual git hook
cat > .git/hooks/pre-commit << 'EOF'
#!/usr/bin/env bash
set -e
echo "[gitleaks] Scanning staged changes for secrets..."
if ! command -v gitleaks &>/dev/null; then
echo "[gitleaks] WARNING: gitleaks not installed, skipping scan"
exit 0
fi
gitleaks detect --staged --config .gitleaks.toml --redact --exit-code 1
if [ $? -ne 0 ]; then
echo ""
echo "[gitleaks] BLOCKED: Secrets detected in staged files."
echo " Review findings above, remove secrets, then commit again."
echo " Use 'git diff --cached' to inspect staged changes."
exit 1
fi
echo "[gitleaks] No secrets found — commit allowed."
EOF
chmod +x .git/hooks/pre-commitTest it by staging a file with a fake secret:
echo 'API_KEY=ghp_faketoken1234567890abcdefghijklmno' >> /tmp/test_secret.txt
git add /tmp/test_secret.txt
git commit -m "test"
# → Should be blocked by gitleaks
git restore --staged /tmp/test_secret.txt
rm /tmp/test_secret.txtOption B: pre-commit framework (recommended for teams)
If your team uses the pre-commit framework, add Gitleaks as a hook in .pre-commit-config.yaml:
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.21.2
hooks:
- id: gitleaksInstall and activate:
pip install pre-commit
pre-commit install
pre-commit run --all-files # one-time scan of all filesThe pre-commit framework handles hook versioning, updates, and cross-platform compatibility automatically — strongly recommended for team repositories.
Step 6 — GitHub Actions CI/CD Integration
Add Gitleaks to your CI pipeline so every pull request is scanned, and pushes to protected branches are blocked on findings.
Create .github/workflows/gitleaks.yml:
name: Secret Scanning (Gitleaks)
on:
push:
branches:
- main
- master
- "release/**"
pull_request:
branches:
- main
- master
jobs:
gitleaks:
name: Detect secrets
runs-on: ubuntu-latest
permissions:
contents: read
security-events: write # Required for SARIF upload
steps:
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history — required for git log scanning
- name: Run Gitleaks
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# Optional: use a custom config
# GITLEAKS_CONFIG: .gitleaks.toml
# Optional: fail the workflow on findings (default: true)
# GITLEAKS_ENABLE_UPLOAD_ARTIFACT: true
# GITLEAKS_ENABLE_SUMMARY: trueThe official gitleaks/gitleaks-action uploads SARIF results to GitHub's Security tab so findings appear in the Code Scanning dashboard alongside other SAST alerts.
PR comment on findings
To get inline PR comments when secrets are found, add the GITHUB_TOKEN environment variable and ensure your repository has the Security tab enabled:
- name: Run Gitleaks with PR annotations
uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITLEAKS_ENABLE_COMMENTS: trueStep 7 — Handling True Positives (Rotating Leaked Secrets)
When Gitleaks finds a real secret in history, rotation is the only safe response — removing it from history is not enough. Even if you rewrite history, the secret may already be in other clones, forks, or CI caches.
Immediate response checklist:
- Revoke / rotate the secret immediately — treat it as compromised
- Check access logs — review the service's audit logs for unauthorized use since the commit timestamp
- Remove from history (stops future exposure, does not undo past exposure):
# Using git-filter-repo (recommended over BFG) pip install git-filter-repo git filter-repo --path config/settings.py --invert-paths # remove file entirely # OR replace the secret value across all history: git filter-repo --replace-text <(echo 'ghp_realtoken==>REDACTED') - Force-push all branches and notify collaborators to re-clone
- Add the fingerprint to
.gitleaks.tomlallowlist after rotation to suppress historical findings:[allowlist] regexes = ["OLD_ROTATED_SECRET_VALUE"]
Verification
After configuring Gitleaks, verify everything is working:
# 1. Confirm binary works
gitleaks version
# 2. Full history scan — confirm no findings (or review any that appear)
gitleaks git --source . --config .gitleaks.toml --verbose
# 3. Test pre-commit hook
echo 'STRIPE_KEY=sk_live_fakeXXXXXXXXXXXXXXXXXXXXXXXX' > /tmp/fake_secret.env
git add /tmp/fake_secret.env
git commit -m "should fail"
# Expected: commit blocked with findings listed
# Clean up test
git restore --staged /tmp/fake_secret.env
rm /tmp/fake_secret.env
# 4. Confirm GitHub Actions workflow appears in Actions tab after pushing
git push origin main
# → Navigate to Actions → Secret Scanning (Gitleaks) → verify green or review findingsExpected output on a clean repository:
○
│╲
│ ○
○ ░
░ gitleaks
3:14PM INF 47 commits scanned.
3:14PM INF scan completed in 320ms
3:14PM INF no leaks found
Troubleshooting
"gitleaks: command not found" in CI
The GitHub Action pulls the binary automatically — this error only occurs with the manual binary approach. Check $PATH or use the full path /usr/local/bin/gitleaks.
Too many false positives
Tune your .gitleaks.toml allowlist by path, regex, or fingerprint. Start with the --verbose flag to see every file scanned, then narrow down which rules are firing on test data.
# Show which rule matched and why
gitleaks git --source . --verbose 2>&1 | grep "RuleID\|File\|Line"Pre-commit hook not running
Check the hook file has execute permissions and is in the right location:
ls -la .git/hooks/pre-commit
# Should show: -rwxr-xr-x
chmod +x .git/hooks/pre-commitIf using the pre-commit framework, ensure it's installed for the current user:
pre-commit install --overwriteScan is very slow on large repositories
Limit the scan scope using --log-opts to scan only recent commits:
# Scan only commits from the last 30 days
gitleaks git --source . --log-opts="--since=30.days.ago"
# Or scan only commits since a known-clean tag
gitleaks git --source . --log-opts="v2.0.0..HEAD"SARIF upload fails in GitHub Actions
Ensure the workflow has security-events: write permission and that GitHub Advanced Security (or free public repo access) is enabled for the repository.
Finding a secret I want to allowlist
Copy the Fingerprint field from the finding output and add it to .gitleaks.toml:
[allowlist]
# Format: <commit>:<file>:<rule>:<line>
stopwords = [
"a3f2c1d9:config/settings.py:github-pat:14"
]Summary
Gitleaks provides a practical, low-friction layer of defense against one of the most common sources of credential compromise in software development. By deploying it at three points — pre-commit (developer workstation), CI pipeline (pull request gate), and scheduled history scans (catch regressions) — you close the loop on secret leakage regardless of where in the workflow a mistake occurs.
Key takeaways:
- Secrets in git history are secrets forever — rotation is the only remedy for a confirmed leak, not history rewriting alone
- Custom rules in
.gitleaks.tomllet you enforce organization-specific secret patterns beyond the built-in 150+ rules - Pre-commit hooks stop secrets at the source before they ever leave a developer's machine
- CI/CD integration with SARIF upload surfaces findings in GitHub's Security tab alongside other code scanning results
- Allowlisting by fingerprint is safer than allowlisting by regex — it targets a specific occurrence rather than suppressing an entire pattern
Combine Gitleaks with a secrets manager (HashiCorp Vault, AWS Secrets Manager, Azure Key Vault) and you eliminate the root cause: secrets no longer need to live in configuration files or environment files that developers might accidentally commit.