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

Updated October 20, 2026

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.

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 10000              # the most history one run can cover (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.

HalfWhat it reads
Working treeEvery tracked file, whole — not just added lines.
HistoryThe 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

FlagDefaultNotes
--no-worktreeoffSkip the working-tree half.
--no-historyoffSkip the commit-history half. Disabling both is an error.
--patternsoffPrint the effective pattern set (built-ins + your guard.secret_patterns) and exit.
--max-commits <N>200Bound the history half. 0 means the default of 200, not unlimited; pass a larger number (up to 10000) to scan more. Truncation is always stated in the human report.
--fail-on <sev>anySee Exit code — the default differs from every other command.
--sarifoffWrite the findings as a SARIF 2.1.0 log for GitHub Code Scanning. Requires v1.21.0 or later. See SARIF output.

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. --quiet drops the coverage and skipped-files lines but never the truncation warnings.

The commit walk itself stops at 10000 commits, a ceiling --max-commits cannot lift. On a longer history the report adds a ⚠ line saying only the newest 10000 commits were inspected and older history was not scanned (Requires v1.21.0 or later).

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. When the commit walk hit its 10000-commit ceiling, meta.walk_truncated is true; it is absent otherwise (Requires v1.21.0 or later). When --max-commits cut the selected history short, meta.truncated is true; it is absent when the window covered the whole history (Requires v1.21.1 or later). The two are independent: a larger --max-commits lifts the second, nothing lifts the first. That reuse is deliberate: a separate scan schema would have bought nothing and broken this pipeline.

SARIF output

Requires v1.21.0 or later. --sarif writes the findings as a SARIF 2.1.0 log under the commitbrief/leaks/ category; use -o to write a file. The human report moves to stderr and --fail-on still sets the exit code. It does not combine with --json or --patterns.

commitbrief leaks --sarif -o leaks.sarif

Each result uses the rule id sec.hardcoded-secret and carries the pattern names and, for a history hit, the short commit hash. It never carries the matched text, a snippet or an author.

Code Scanning treats an alert missing from the upload as fixed, so a partial scan would close alerts it never checked. --sarif therefore writes a log only for a complete scan:

  • Combining it with --no-worktree, --no-history, --file, --dir, --exclude-file, --exclude-dir, --author, --committer, --start-date, --end-date, --text or a git range is a usage error.
  • If the history scan stops at the --max-commits window, no file is written and the command exits 1. Re-run with a larger --max-commits N.
  • If the history is longer than the commit walk can cover, no file is written and the command exits 1. --max-commits cannot raise that limit.

Upload it with category: commitbrief/leaks. See SARIF and Code Scanning.

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 always stated in the human report, --quiet included; under --json it is flagged as meta.truncated, and the walk ceiling as meta.walk_truncated. The walk itself stops at 10000 commits whatever --max-commits says.

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