Gitleaks
.mdVerified against Gitleaks 8.30.1, flags verified via `gitleaks --help` / `gitleaks detect --help` run · official docs
What it is and where it fits 🎯#
Gitleaks is the fast, regex-and-entropy-based secret scanner most teams reach for first, because it's simple to reason about and trivial to drop into a pre-commit hook. It's the classic fix for the exact failure mode this series' own DevSecOps tutorial opens with: a credential gets hardcoded, merged, and discovered days later by chance — Gitleaks catches it in seconds, locally, before the commit even lands. Where it differs from its sibling TruffleHog: Gitleaks reports pattern matches (a string that looks like an AWS key), not confirmed-live credentials — faster and simpler, at the cost of more false positives on ambiguous-looking strings. See the TruffleHog cheat sheet for the live-verification alternative.
Installation#
apt-get install gitleaks # Debian/Ubuntu, if the package is in your repos
brew install gitleaks # macOS/Linux Homebrew
# or download the binary release directly:
curl -sLo gitleaks.tar.gz \
https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks_8.30.1_linux_x64.tar.gz
tar -xzf gitleaks.tar.gz gitleaks && sudo mv gitleaks /usr/local/bin/
gitleaks versionNote
There's no official curl install-script; pin the exact release filename from the GitHub Releases page rather than guessing the version in the tarball URL — the filename embeds the version number directly.
Scanning git history#
gitleaks git . # full history of the current repo
gitleaks git --log-opts="--since=2026-01-01" . # limit the history range scannedgitleaks git walks every commit's diff, not just the current working tree — this is the mode that catches a
secret that was committed and then "removed" in a later commit, since it's still sitting in git history and
fully retrievable by anyone with clone access.
Scanning a working directory (no git history)#
gitleaks dir . # every file on disk, as-is
gitleaks dir . --no-git # equivalent — treat as a plain directory even inside a git repoScanning stdin#
cat some_file | gitleaks stdin
git diff --staged | gitleaks stdin # scan only what's about to be committed — the pre-commit-hook shapePre-commit hook usage 🧪#
# .pre-commit-config.yaml
repos:
- repo: https://github.com/gitleaks/gitleaks
rev: v8.30.1
hooks:
- id: gitleaks- Install
pre-commit(pipx install pre-commit) and runpre-commit installonce per clone - Commit
.pre-commit-config.yamlso every contributor gets the hook automatically - Run
pre-commit run --all-filesonce after adding it, to catch anything already in the working tree
Output and CI gating#
gitleaks git . --report-format json --report-path report.json
gitleaks git . --exit-code 1 # default is already 1 on leaks found, 0 clean — set explicitly if scripting
gitleaks git . -v # verbose — print each finding, not just a summarySample output shape (illustrative — real rule IDs and match counts depend on what's actually in the repo):
Finding: AKIA************WXYZ
Secret: AKIAIOSFODNN7EXAMPLE
RuleID: aws-access-token
Entropy: 3.646430
File: config/settings.py
Line: 42
Commit: a1b2c3d4e5f6...
Author: Jane Doe
Date: 2026-03-14T10:15:00Z
○
│╲
│ ○
○ ░
░ gitleaks
10:15AM INF 1 commits scanned.
10:15AM WARN leaks found: 1
Reducing false positives#
gitleaks git . --baseline-path .gitleaks-baseline.json # ignore known/accepted findings, only report new ones
gitleaks git . -i .gitleaksignore # ignore specific finding fingerprints by file
gitleaks git . --redact # redact the actual secret value from output (default: on)A # gitleaks:allow comment on the offending line suppresses that specific match:
API_KEY = "AKIAIOSFODNN7EXAMPLE" # gitleaks:allow — test fixture, not a real credentialCaution
Use gitleaks:allow sparingly and only for genuinely fake/example credentials (test fixtures, docs
examples). Permanently allow-listing a real secret instead of rotating and removing it defeats the entire
point of the scanner — it's a suppression mechanism for false positives, not an escape hatch for real ones.
Custom detection rules#
Gitleaks ships a broad default rule set (.gitleaks.toml-shaped), but a team can extend it for internal
credential formats a generic ruleset wouldn't recognize:
# .gitleaks.toml
[[rules]]
id = "internal-api-token"
description = "Internal platform API token"
regex = '''internal_[a-z0-9]{32}'''
tags = ["key", "internal"]
[allowlist]
paths = [
'''(.*?)(test|fixture)(.*?)''',
]gitleaks git . -c .gitleaks.toml # use the custom config instead of the built-in default rulesReal-world scenario: GitHub Actions CI gate#
# .github/workflows/gitleaks.yml
name: Gitleaks
on: [pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # full history — a shallow clone hides everything but the latest commit
- uses: gitleaks/gitleaks-action@v2
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}Important
fetch-depth: 0 matters here specifically — GitHub Actions checks out a shallow clone by default
(usually depth 1), which means gitleaks git only ever sees the single latest commit. A secret introduced
two commits ago and "fixed" since would be invisible to the scan without the full history.
Common pitfalls#
- Running
gitleaks dirwhen you meantgitleaks git—dirnever looks at history at all, so a secret removed in a later commit (but still in history) goes undetected. - Shallow CI checkouts — see the IMPORTANT callout above.
- Over-allowlisting — a
.gitleaksignoreor config allowlist that's too broad (a wide path regex) can silently blind the scanner to real findings in that path going forward.
Exit codes#
0 no leaks found · 1 leaks found (configurable via --exit-code).
When to reach for something else#
If false-positive noise from pattern-only matching is a real adoption blocker, TruffleHog's live-verification
mode (--only-verified) trades a network call per candidate for far higher confidence per finding — see its
cheat sheet. Semgrep's p/secrets ruleset overlaps here too but is a much lighter-weight community set, not a
dedicated secrets engine.