Find leaked secrets in a git repo with gitleaks
Scan git history and working files for API keys and tokens with gitleaks, allowlist false positives, add a pre-commit hook and run it in CI.
Updated
in short
Run gitleaks git -v to scan every commit and gitleaks dir -v to scan files on disk. Allowlist false positives in .gitleaks.toml, block new leaks with a pre-commit hook, and run the same scan in CI.
A secret committed once stays in git history after you delete the file. gitleaks scans the patches from git log -p or plain files for API keys, tokens and passwords using a built-in set of rules, and exits non-zero when it finds something, so the same command works by hand, in a hook and in CI.
This guide uses the git and dir subcommands added in gitleaks 8.19.0. Older releases use detect and protect; the translation is in the FAQ.
Tools used
Before you start
- git, and a repository to scan
- gitleaks 8.19.0 or newer for the
gitanddirsubcommands; 8.25.0 or newer for the[[allowlists]]config syntax - Python, if you want to use the pre-commit framework in step 6
Steps
Step 1: Install gitleaks
Homebrew and the Docker images track current releases, and prebuilt binaries for Linux, macOS and Windows are on the GitHub releases page. Some distribution packages lag far behind (Debian 13 ships 8.16), so check the version before following the rest of this guide.
shbrew install gitleaks gitleaks versionsh# Docker: mount the repo and pass the subcommand docker run --rm -v "$PWD:/path" ghcr.io/gitleaks/gitleaks:latest git -v /pathStep 2: Scan the full git history
gitleaks gitrunsgit log -pand scans every added line in every commit. With no path it scans the repository in the current directory.-vprints each finding, and--redacthides the secret value so it does not end up in terminal scrollback or CI logs.Each finding shows the rule, file, line, commit, author and a
Fingerprintyou can use to ignore it later. The exit code is 1 when leaks are found and 0 when none are.shcd my-repo gitleaks git -v --redacttextFinding: token = "REDACTED Secret: REDACTED RuleID: github-pat Entropy: 5.171928 File: config.py Line: 1 Commit: 3f33a0e586970f793250f5ec9c380b4abb60a227 Author: dev Email: dev@example.com Date: 2026-10-03T02:22:00Z Fingerprint: 3f33a0e586970f793250f5ec9c380b4abb60a227:config.py:github-pat:1Step 3: Scan only some commits
--log-optspasses options straight togit log. Use--allto include every branch, or a range to scan only what a branch adds. This is the usual setup for pull requests, where scanning the full history on every run is slow.shgitleaks git -v --log-opts="--all" gitleaks git -v --log-opts="origin/main..HEAD"Step 4: Scan files on disk
gitleaks dirscans files as they are now, including untracked and uncommitted files, and works on directories that are not git repos. Pass a path, or nothing for the current directory.gitleaks stdinscans piped input.shgitleaks dir -v . gitleaks dir -v ./deploy/values.yaml cat dump.sql | gitleaks stdin -vStep 5: Allowlist false positives
Put a
.gitleaks.tomlat the repo root; gitleaks loads it automatically when it scans that path.[extend] useDefault = truekeeps the built-in rules and adds yours on top.[[allowlists]]entries skip findings by path, commit, regex or stopword. On releases before 8.25.0 the global table is a single[allowlist].For one-off cases, add a
gitleaks:allowcomment on the line, or put the finding'sFingerprintin a.gitleaksignorefile at the repo root.toml# .gitleaks.toml [extend] useDefault = true [[allowlists]] description = "test fixtures and example values" paths = [ '''(^|/)testdata/''', '''\.example$''', ] regexTarget = "line" regexes = [ '''EXAMPLE_KEY_DO_NOT_USE''', ]pythonTEST_TOKEN = "ghp_000000000000000000000000000000000000" # gitleaks:allowtext# .gitleaksignore 3f33a0e586970f793250f5ec9c380b4abb60a227:config.py:github-pat:1Step 6: Accept existing findings with a baseline
In an old repository you may want to stop new leaks before you finish cleaning up the history. Save a report, then pass it as
--baseline-path; later scans report only findings that are not in the baseline. Write the report outside the repo, or a latergitleaks dirscan will flag the secrets inside the report itself.shgitleaks git --report-format json --report-path ../gitleaks-baseline.json gitleaks git -v --baseline-path ../gitleaks-baseline.jsonStep 7: Block new secrets before each commit
With the pre-commit framework, add the gitleaks hook and run
pre-commit install. The hook runsgitleaks git --pre-commit --redact --staged --verbose, which scans only staged changes. Use thegitleaks-systemhook ID to run the gitleaks binary already on yourPATH, orgitleaks-dockerfor the container image.pre-commit autoupdatebumpsrevto the latest tag.To skip it once for a known false positive, run
SKIP=gitleaks git commit ....yaml# .pre-commit-config.yaml repos: - repo: https://github.com/gitleaks/gitleaks rev: v8.30.1 hooks: - id: gitleaksshpre-commit installsh# without the framework: scan staged changes directly gitleaks git --pre-commit --staged --redact -vStep 8: Run it in CI
On GitHub,
gitleaks/gitleaks-actionscans pushes and pull requests and can comment on PRs. Check out the full history withfetch-depth: 0. The action needs aGITLEAKS_LICENSEsecret for repositories owned by an organization, not for personal accounts.In any other CI, run the binary or Docker image. It exits 1 on findings, which fails the job;
--report-format sarifproduces a file that code-scanning dashboards can read.yaml# .github/workflows/gitleaks.yml name: gitleaks on: [pull_request, push, workflow_dispatch] jobs: scan: 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 }} # organizations onlyshgitleaks git --redact -v --report-format sarif --report-path gitleaks.sarif
If a scan finds a real secret
- Revoke or rotate the credential first. Anyone who cloned or forked the repo already has the old history.
- Then remove it from the code and load it from an environment variable or a secrets manager.
- Rewriting history is a separate, disruptive step; do it only after the key is dead, and coordinate with everyone who has a clone.
Troubleshooting
- gitleaks rejects
gitordiras an unknown command: your version is older than 8.19.0. Upgrade, or usegitleaks detectandgitleaks protect. - Exit code 126: gitleaks did not recognise a flag. Check
gitleaks git --helpfor your version. .gitleaks.tomlis ignored: it is only picked up from the scanned path. Pass-c path/to/.gitleaks.tomlor setGITLEAKS_CONFIG.- Your rules replaced the defaults: add
[extend]withuseDefault = trueso the built-in rules still run. - A CI scan only covers the latest commit: the checkout is shallow. Set
fetch-depth: 0, or fetch enough history for the range you pass to--log-opts. - Too many generic findings: list noisy rules in
disabledRulesunder[extend], for exampledisabledRules = ["generic-api-key"].
Also worth a look
- git-secretsInstalls git hooks that reject commits matching prohibited patterns, with AWS patterns built in.
- trivySecurity scanner whose `--scanners secret` mode finds secrets alongside vulnerabilities and misconfigurations.
FAQ
- What replaced `gitleaks detect` and `gitleaks protect`?
- Since 8.19.0, `detect` is `gitleaks git`, `detect --no-git` is `gitleaks dir`, `protect` is `gitleaks git --pre-commit`, and `protect --staged` is `gitleaks git --pre-commit --staged`. The old commands still run but are hidden from `--help`.
- What is the difference between `gitleaks git` and `gitleaks dir`?
- `git` scans lines added in commits, so it finds secrets that were deleted later. `dir` scans files as they exist on disk now, including untracked files, but sees no history.
- Is gitleaks still maintained?
- Its README says it is feature complete and future releases will be security patches only; the author has started a new project called Betterleaks. Current releases keep working with the commands in this guide.