// docs · v1.x

Credential audit — leaks

commitbrief leaks scans every tracked file plus the added lines of your git history for committed credentials — the same pattern set the review path uses, with no provider call, no cache, and no cost. Exits 1 on a hit, so it gates CI out of the box.

commitbrief leaks audits the repository for committed credentials — every tracked file in the working tree, plus the lines added by historical commits. It is fully deterministic: the same pattern set the secret scanner uses, no provider call, no cache, no cost. New in v1.15.0 (ADR-0036).

Why this is a separate command

The pre-send secret scanner is a gate. It runs before a review is sent and sees exactly one thing: the added lines of that diff. That is the right shape for stopping a leak in flight, and the wrong shape for two questions people actually ask:

  • Is there a secret in my working tree right now? The gate never reads files — only a diff.
  • Did anyone ever commit one? The gate reads the added lines of one diff. A key that was committed and later removed is still in the history — and still reachable by anyone who cloned or forked the repo before you scrubbed it.

leaks answers both.

Usage

commitbrief leaks                                  # tracked files + last 200 commits
commitbrief leaks --no-history                     # working tree only, fast
commitbrief leaks main..HEAD --no-worktree         # exactly that range
commitbrief leaks --author alice --start-date 2026-01-01
commitbrief leaks --max-commits 0                  # the whole history (slow)
commitbrief leaks --patterns                       # what it looks for, then exit

The two halves

Both run by default, and each has its own off-switch — so a positional range narrows the history half without silently disabling the tree.

Half What it reads
Working tree Every tracked file, whole — not just added lines.
History The added lines of each selected commit, attributed to its hash, author, and date.

Untracked files are deliberately out of scope. An untracked, gitignored .env is where a secret is supposed to live, and it cannot leak through git — flagging it would be noise.

Added lines only, for history, is precisely how a removed-but-committed key is found. Scanning context lines instead would re-report the same key once per commit that happened to touch the file near it.

Flags

Flag Default Notes
--no-worktree off Skip the working-tree half.
--no-history off Skip the commit-history half. Disabling both is an error.
--patterns off Print the effective pattern set (built-ins + your guard.secret_patterns) and exit.
--max-commits <N> 200 Bound the history half. 0 walks everything. Truncation is always reported.
--fail-on <sev> any See Exit code — the default differs from every other command.

Every filter applies. The commit filters (--author, --committer, --start-date, --end-date, --text) narrow which commits the history half reads; the path filters (--file, --dir, --exclude-file, --exclude-dir) narrow which files either half reads.

Output

Scanned 613 file(s) across 50 commit(s).
Skipped 4 file(s): binary or over the size cap.

  CRITICAL  config/prod.yml:14                      AWS Access Key
  CRITICAL  deploy/legacy.env:3                     Stripe Live Key  (a1b2c3d, alice)

2 possible credential(s) found. Rotate them — a key in git history stays reachable in every existing clone.

The trailing (hash, author) marks a history hit: the line may be long gone from your working tree. Coverage is always stated — files scanned, commits scanned, files skipped, and whether the walk was truncated — because “no credentials found” means nothing without knowing what was looked at.

What it never prints

The matched text. A finding carries a file, a line, and the pattern names — nothing else. The scanner reads whole files, so its own report must not become a second copy of the secret in your terminal scrollback, your CI log, or the pastebin you were about to send it to. In --json, snippet is always absent for the same reason.

Severity

Hits are critical, with two deliberate exceptions at high:

  • JWT — expired and sample tokens are common in fixtures; it is the one built-in with a real false-positive rate.
  • User patterns from guard.secret_patterns — CommitBrief cannot judge how bad a hit on your house format is.

Exit code

leaks is the one command where --fail-on defaults to any: a hit exits 1, so it gates CI with no extra configuration. The report is printed first, then the run fails — you get the findings and the non-zero exit.

commitbrief leaks                     # exit 1 if anything is found
commitbrief leaks --fail-on none      # report only, always exit 0
commitbrief leaks --fail-on critical  # ignore the `high` tier

Feeding the policy gate

--json emits the standard schema v1 document, so guard consumes it directly with no new plumbing:

commitbrief leaks --json --fail-on none | commitbrief guard --from-json -

meta.provider is "builtin" and cost_usd is 0 — there was no model call. That reuse is deliberate: a separate scan schema would have bought nothing and broken this pipeline.

Limits — read these before trusting a clean report

  • The ignore layers apply. leaks honors the built-in patterns and .commitbriefignore exactly as a review does, so a key committed into vendor/**, dist/**, or a *.min.js is not reported. This keeps .commitbriefignore the single place a project says “not this”. Narrow with --dir if you need the wider sweep.
  • Regex only, no entropy scoring. A high-entropy blob with no recognizable prefix is invisible. The patterns are deliberately tight — length floors and fixed prefixes — which is what keeps false positives rare. This is a targeted check, not a general-purpose secret scanner.
  • Binaries and large files are skipped (a NUL byte in the first 8 KiB, or over 5 MiB). Both are counted in the report.
  • Untracked files are not scanned.
  • --max-commits bounds history at 200 by default. Truncation is reported, never silent.

If it finds something

Rotate the credential at its provider. That is the fix.

Removing the line, amending the commit, or rewriting history does not reach clones, forks, CI caches, or anyone’s local checkout that already has the commit. Treat a committed key as compromised from the moment it was pushed.

See also