# 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 docs · v1.x · Operations

Canonical URL: https://commitbrief.com/docs/1.x/leaks/

---

`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](/docs/1.x/safety-and-cost#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

```sh
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](#exit-code) — the default differs from every other command. |

Every [filter](/docs/1.x/review-scopes) applies. The
[commit filters](/docs/1.x/review-scopes#commit-filters-v1150)
(`--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

```text
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.

```sh
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](/docs/1.x/output-formats#json-schema-v1) document, so
[`guard`](/docs/1.x/policy-gate) consumes it directly with no new
plumbing:

```sh
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

- [Safety, cost, and cache](/docs/1.x/safety-and-cost#secret-scanner) —
  the pre-send gate this complements, and where you register your own
  patterns.
- [Review scopes](/docs/1.x/review-scopes) — the commit and path filters
  that narrow a scan.
- [Policy gate — guard](/docs/1.x/policy-gate) — the gate `--json` feeds.
- [Output formats](/docs/1.x/output-formats#json-schema-v1) — the
  document `--json` emits.