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.

how-to8 steps

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 git and dir subcommands; 8.25.0 or newer for the [[allowlists]] config syntax
  • Python, if you want to use the pre-commit framework in step 6

Steps

  1. 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.

    sh
    brew install gitleaks
    gitleaks version
    sh
    # Docker: mount the repo and pass the subcommand
    docker run --rm -v "$PWD:/path" ghcr.io/gitleaks/gitleaks:latest git -v /path
  2. Step 2: Scan the full git history

    gitleaks git runs git log -p and scans every added line in every commit. With no path it scans the repository in the current directory. -v prints each finding, and --redact hides 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 Fingerprint you can use to ignore it later. The exit code is 1 when leaks are found and 0 when none are.

    sh
    cd my-repo
    gitleaks git -v --redact
    text
    Finding:     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:1
  3. Step 3: Scan only some commits

    --log-opts passes options straight to git log. Use --all to 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.

    sh
    gitleaks git -v --log-opts="--all"
    gitleaks git -v --log-opts="origin/main..HEAD"
  4. Step 4: Scan files on disk

    gitleaks dir scans 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 stdin scans piped input.

    sh
    gitleaks dir -v .
    gitleaks dir -v ./deploy/values.yaml
    cat dump.sql | gitleaks stdin -v
  5. Step 5: Allowlist false positives

    Put a .gitleaks.toml at the repo root; gitleaks loads it automatically when it scans that path. [extend] useDefault = true keeps 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:allow comment on the line, or put the finding's Fingerprint in a .gitleaksignore file 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''',
    ]
    python
    TEST_TOKEN = "ghp_000000000000000000000000000000000000"  # gitleaks:allow
    text
    # .gitleaksignore
    3f33a0e586970f793250f5ec9c380b4abb60a227:config.py:github-pat:1
  6. Step 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 later gitleaks dir scan will flag the secrets inside the report itself.

    sh
    gitleaks git --report-format json --report-path ../gitleaks-baseline.json
    gitleaks git -v --baseline-path ../gitleaks-baseline.json
  7. Step 7: Block new secrets before each commit

    With the pre-commit framework, add the gitleaks hook and run pre-commit install. The hook runs gitleaks git --pre-commit --redact --staged --verbose, which scans only staged changes. Use the gitleaks-system hook ID to run the gitleaks binary already on your PATH, or gitleaks-docker for the container image. pre-commit autoupdate bumps rev to 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: gitleaks
    sh
    pre-commit install
    sh
    # without the framework: scan staged changes directly
    gitleaks git --pre-commit --staged --redact -v
  8. Step 8: Run it in CI

    On GitHub, gitleaks/gitleaks-action scans pushes and pull requests and can comment on PRs. Check out the full history with fetch-depth: 0. The action needs a GITLEAKS_LICENSE secret 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 sarif produces 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 only
    sh
    gitleaks 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 git or dir as an unknown command: your version is older than 8.19.0. Upgrade, or use gitleaks detect and gitleaks protect.
  • Exit code 126: gitleaks did not recognise a flag. Check gitleaks git --help for your version.
  • .gitleaks.toml is ignored: it is only picked up from the scanned path. Pass -c path/to/.gitleaks.toml or set GITLEAKS_CONFIG.
  • Your rules replaced the defaults: add [extend] with useDefault = true so 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 disabledRules under [extend], for example disabledRules = ["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.

related