The gitleaks pre-commit hook blocks a commit because tests/fixtures/jwt.json contains a sample token, CI fails on a lockfile's integrity hashes flagged as generic-api-key, and after the third false alarm in a week someone adds --no-verify to their commit alias. A secret scanner that cries wolf gets bypassed, and a bypassed scanner catches nothing. The fix is not to loosen detection globally but to allowlist exactly the known-safe matches — narrowly enough that a real secret in the same file still fails. This page does that with gitleaks, as part of secret scanning and leak prevention.

Every allowlist entry is a small, deliberate hole in detection; the goal is to make each one as small and as reviewable as possible.

Diagnostic

Run the scanner over the repository and group findings by rule and path to see what the noise is:

#!/usr/bin/env bash
set -euo pipefail
gitleaks detect --source . --redact --report-format json --report-path /tmp/leaks.json --exit-code 0
jq -r '.[] | "\(.RuleID)\t\(.File | split("/")[0:2] | join("/"))"' /tmp/leaks.json | sort | uniq -c | sort -rn | head -8
jq -r '.[0] | "fingerprint: \(.Fingerprint)"' /tmp/leaks.json
git config --get-regexp 'alias\.' | grep -- '--no-verify' || true

Expected bad output:

     14 generic-api-key	package-lock.json
      6 jwt	tests/fixtures
      3 generic-api-key	docs/examples
      1 aws-access-token	scripts/seed.sh
fingerprint: 3f2a91c7b4e8d1a2:package-lock.json:generic-api-key:1842
alias.ci commit --no-verify

Twenty-three findings are noise from lockfile hashes, test fixtures and documentation; one in scripts/seed.sh may be real — and it is buried. Someone has already aliased around the hook.

Findings by Source Before Tuning Bar chart of gitleaks findings grouped by where they occur in the repository. Findings by Source Before Tuning package-lock.json 14 tests/fixtures 6 docs/examples 3 scripts/seed.sh 1 real?
Noise from three predictable sources hid the one finding that needed attention.

Root cause

Secret scanners combine precise rules for known formats (AWS keys, GitHub tokens, Stripe keys) with generic high-entropy rules for anything that looks like a credential. Generic rules are what catch custom tokens, and they also match integrity hashes, UUIDs, base64 test data and example keys in documentation. When the configuration has no allowlists, every such match fails the hook, and developers learn that failures are usually noise. The typical overreaction — disabling a rule, excluding a whole directory, or bypassing the hook — removes real protection along with the noise. Precise tuning is possible because false positives are predictable: they come from specific files, specific value formats or specific, reviewed values.

The social dynamics matter as much as the regexes. A hook that fails on noise teaches developers that the fix for a red hook is to get around it, and that lesson generalises: the day a real key is staged, the reflex is the same bypass. Every false positive therefore costs more than the minute it takes to dismiss — it erodes the one behaviour the scanner depends on. Treating false positives as bugs in the configuration, fixed promptly and narrowly by the team that owns the scanner, keeps the hook credible. The measure of success is not zero findings but a hook whose failures people take seriously.

Resolution

  1. Extend the default rules instead of replacing them, and allowlist predictable safe formats with narrow regexes:
title = "acme gitleaks config"

[extend]
useDefault = true

[allowlist]
description = "Known-safe formats and generated files"
paths = [
  '''(^|/)package-lock\.json$''',
  '''(^|/)pnpm-lock\.yaml$''',
  '''(^|/)go\.sum$''',
]
regexes = [
  '''sha(256|384|512)-[A-Za-z0-9+/=]{40,}''',
  '''AKIAIOSFODNN7EXAMPLE''',
]

Save as .gitleaks.toml. Lockfiles contain only integrity hashes and package URLs, so excluding them is safe; AKIAIOSFODNN7EXAMPLE is AWS's documented example key.

  1. Scope test fixtures narrowly — by path and rule — so a real key accidentally pasted into a fixture of a different type still fails:
[[rules]]
id = "jwt"
[rules.allowlist]
paths = ['''^tests/fixtures/.*\.json$''']
regexes = ['''eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9\.eyJzdWIiOiJ0ZXN0[^"]*''']

The regex matches only tokens whose payload starts with {"sub":"test, the convention the test suite uses for fixture tokens.

  1. Ignore individual reviewed findings by fingerprint when a one-off safe match does not fit a pattern:
#!/usr/bin/env bash
set -euo pipefail
jq -r '.[] | select(.File | startswith("docs/examples/")) | .Fingerprint' /tmp/leaks.json >> .gitleaksignore
sort -u -o .gitleaksignore .gitleaksignore
wc -l .gitleaksignore

Fingerprints include commit, file, rule and line, so the ignore applies to that exact occurrence only.

  1. Mark intentional inline values with the gitleaks:allow comment, which is visible in review:
const exampleKey = 'sk_test_EXAMPLE0000000000000000'; // gitleaks:allow — placeholder used in the README example
  1. Investigate the remaining real finding — here scripts/seed.sh — and rotate it if it is a live credential, as in removing a leaked API key from git history.
Choosing the Narrowest Allowlist Decision diagram choosing a path, regex, fingerprint or inline allowlist for a false positive. Choosing the Narrowest Allowlist What kind of false positive? generated file only path allowlist recognisable format regex allowlist one reviewed match fingerprint or inline
Prefer the narrowest mechanism that fits; broad exclusions hide real leaks.

Expected output

$ gitleaks detect --source . --redact --config .gitleaks.toml
INF 1 commits scanned.
INF scan completed in 1.2s
WRN leaks found: 1
Finding:     AWS_KEY=REDACTED
RuleID:      aws-access-token
File:        scripts/seed.sh

The twenty-three false positives are gone and the one genuine-looking finding stands alone, where it gets attention instead of being dismissed with the rest.

With noise near zero, the hook's failures regain their meaning: a blocked commit almost always means something worth looking at. That is when developers stop bypassing it, and the --no-verify alias can be removed without resistance. Track the false-positive rate over time; if it creeps back up, tune again rather than tolerating it.

Prevention

  1. Require review for allowlist changes with a CODEOWNERS entry for .gitleaks.toml and .gitleaksignore, owned by the security or platform team.

  2. Test the configuration with a known real-looking secret in a scratch file to confirm allowlists did not open a hole:

#!/usr/bin/env bash
set -euo pipefail
tmp=$(mktemp -d); printf 'token = "ghp_%s"\n' "$(head -c 30 /dev/urandom | base64 | tr -dc 'A-Za-z0-9' | head -c 36)" > "$tmp/probe.txt"
gitleaks detect --no-git --source "$tmp" --config .gitleaks.toml --exit-code 1 >/dev/null 2>&1 && echo "PROBLEM: probe not detected" || echo "probe detected: config still catches real tokens"
rm -rf "$tmp"
  1. Never allowlist whole directories of application code. Test and generated paths only; application code gets fingerprint or inline allowances, each visible in review.
Broad Exclusions vs Narrow Allowlists Comparison of disabling rules or excluding directories against narrow path, regex and fingerprint allowlists. Broad Exclusions vs Narrow Allowlists disable generic rule regex for known format exclude tests/ entirely fixtures path plus rule --no-verify alias fingerprint ignore real leaks hidden too real leaks still fail no review trail CODEOWNERS on config
Narrow allowlists remove noise while keeping detection for everything else.

Platform caveats

Pre-commit and CI parity: point both the pre-commit hook and the CI job at the same .gitleaks.toml, or local and CI results diverge; see running the same lint checks in pre-commit and CI.

Monorepos: path allowlists are relative to the scan root; run gitleaks from the repository root so patterns like ^tests/fixtures/ match consistently.

Other scanners: TruffleHog, detect-secrets and GitHub secret scanning have equivalent mechanisms (--exclude-paths, baseline files, push-protection bypass reasons); the same narrowest-first principle applies.

Rollback

Revert the configuration and ignore files to return to default rules; expect the old noise to return:

#!/usr/bin/env bash
set -euo pipefail
git checkout HEAD~1 -- .gitleaks.toml .gitleaksignore

Frequently Asked Questions

How do I stop gitleaks flagging lockfile hashes?

Add the lockfiles to the global paths allowlist, or add a regex for sha512- integrity strings. Lockfiles contain only hashes and package URLs, so excluding them does not hide real secrets.

Is it safe to exclude the whole tests/ directory?

Usually not. Real credentials do end up in test files. Allow specific fixture paths for specific rules, or match the fixture convention with a regex.

What is the difference between .gitleaksignore and gitleaks:allow?

.gitleaksignore lists fingerprints of specific findings and is edited separately from the code. gitleaks:allow is an inline comment on the line itself, visible in the diff that introduces the value. Both apply to one occurrence.

How do I know the allowlist did not hide a real secret?

Run a probe with a realistic fake token through the same configuration in CI; if it is not detected, the allowlist is too broad.