Secrets leak into git repositories all the time. An AWS key pasted into a config file for a quick test, a database password in a docker-compose file, a Slack token in a shell script. Once committed, the secret lives in your git history even after you delete the file. A gitleaks scan digs through every commit and flags those secrets before an attacker finds them. I run one on every repository I inherit, and it finds something embarrassing more often than not.
Gitleaks is a single Go binary with no runtime dependencies. It ships with over 150 detection rules covering AWS, GitHub, Stripe, Slack, GCP, and private key formats, and it checks entropy too, so random-looking strings get flagged even when no vendor rule matches. The project sits at over 28,000 stars on GitHub and is the default secret scanner in a lot of CI pipelines for a reason: it is fast and the false positive rate is manageable.
Here are the eight commands I actually use, from the first scan to full CI enforcement.
1. Install Gitleaks
On Debian and Ubuntu there is no official apt package, so grab the binary from the gitleaks GitHub repository:
wget https://github.com/gitleaks/gitleaks/releases/download/v8.24.3/gitleaks_8.24.3_linux_x64.tar.gz
tar -xzf gitleaks_8.24.3_linux_x64.tar.gz
sudo mv gitleaks /usr/local/bin/
Arch users get it from the repos with pacman -S gitleaks, macOS users with brew install gitleaks. Verify the install:
gitleaks version
2. Run Your First Gitleaks Scan on a Repository
The core command scans the full git history of the current directory:
cd /path/to/repo
gitleaks git -v
The -v flag prints each finding as it appears: the rule that matched, the file, the commit hash, the author, and a redacted preview of the secret. On a medium-sized repo with a few thousand commits, the scan finishes in seconds. Output ends with a summary line like leaks found: 3 and a non-zero exit code, which matters later for CI.
Note the commit hash in each finding. The secret may be gone from the current working tree, but anyone who clones the repo can check out that commit and read it. Rotating the credential is the only real fix.
3. Point a Gitleaks Scan at a Plain Directory
Not everything lives in git. For a directory of config files, backups, or an extracted archive, use dir mode:
gitleaks dir /etc/myapp -v
This skips git history entirely and checks the files as they sit on disk. I use it on /etc after taking over a server, the same way I run a lynis audit to get a baseline picture of what I just inherited. You can also pipe arbitrary data through stdin:
cat suspicious.env | gitleaks stdin -v
4. Check Staged Changes Before You Commit
Catching a secret after it lands in history is damage control. Catching it before the commit is prevention:
gitleaks git --pre-commit --staged -v
Drop that line into .git/hooks/pre-commit and the commit aborts whenever the gitleaks scan spots something that looks like a credential in your staged changes. The hook adds well under a second on typical commits. Of all the commands here, this one has saved me the most cleanup work.
5. Save Gitleaks Scan Results to a Report File
For anything beyond a quick look, write the findings to a structured report:
gitleaks git --report-format json --report-path gitleaks-report.json
Supported formats are JSON, CSV, JUnit, and SARIF. SARIF is the one to pick if you want findings to show up in the GitHub Security tab. The JSON output includes the full secret by default, so treat the report file itself as sensitive. Add --redact to mask the secrets in the report if you plan to attach it to a ticket.
6. Baseline an Existing Repository
Running a first gitleaks scan on a ten-year-old repo can return hundreds of findings, most of them long-rotated test keys. Fixing history is rarely practical. A baseline lets you accept the old findings and only fail on new ones:
gitleaks git --report-path baseline.json
gitleaks git --baseline-path baseline.json --report-path new-findings.json
The second command ignores everything recorded in baseline.json and reports only fresh leaks. Commit the baseline file to the repo so every developer and every CI run works from the same starting point.
7. Handle False Positives
Entropy detection sometimes flags things that are not secrets: example keys in documentation, test fixtures, long random IDs. You have two clean options. For a one-off, add an inline comment on the offending line:
fake_key = "AKIAIOSFODNN7EXAMPLE" # gitleaks:allow
For repeating patterns, create a .gitleaksignore file in the repo root and paste in the fingerprint that gitleaks prints with each finding. One fingerprint per line. This beats loosening the detection rules, because the rules stay strict for everything else.
8. Automate the Gitleaks Scan in CI
Local scans depend on people remembering to run them. CI does not forget. For GitHub repos, the official gitleaks-action needs four lines:
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: gitleaks/gitleaks-action@v2
The fetch-depth: 0 part matters. Without it the runner gets a shallow clone and the gitleaks scan only sees the latest commit instead of the full history. On GitLab or Jenkins, run the binary directly and let the non-zero exit code fail the pipeline. Pair it with a trivy scan in the same stage and you cover both leaked credentials and vulnerable dependencies in one pass.
What a Gitleaks Scan Will Not Do
Gitleaks finds secrets. It does not verify them, rotate them, or remove them from history. A finding tells you a credential-shaped string exists in a commit, not whether that credential still works. Tools like trufflehog take the extra step of testing keys against live APIs, which is useful during incident response but slower and noisier for routine checks.
History rewriting is also out of scope. If a live credential is in your history, rotate it first, then decide whether rewriting with git filter-repo is worth breaking every existing clone. Rotation is mandatory. Rewriting is optional.
Between the pre-commit hook, the baseline workflow, and CI enforcement, a gitleaks scan at each stage costs seconds and closes one of the most common ways credentials end up public. If you audit servers with fail2ban and lynis but have never scanned your git history, start with command number two. The results usually make the case on their own.