Skip to content

Secret scanner

Muhammet Şafak edited this page Jul 30, 2026 · 5 revisions

Home / Operations / Secret scanner

Secret scanner

Pre-send guard that flags credential-shaped strings in the diff before the LLM call. Prevents accidental upload of API keys, private keys, tokens, etc. to a third-party provider.

What it scans

The scanner walks two surfaces:

  1. The diff — only added lines (+-prefixed, excluding the +++ b/path header). Removed and context lines are skipped — the goal is to catch new leaks, not re-flag history that is already on disk somewhere.
  2. User-authored rules content — any non-default COMMITBRIEF.md or OUTPUT.md you have written. These join the system prompt verbatim, so a credential pasted into either file would leak just as surely as one in the diff (UC-05). Embedded defaults are presumed clean and skipped.

Patterns matched

Hardcoded regex list in internal/guard/secrets.go. As of v1.0.0:

Name Pattern (regex) Notes
AWS Access Key AKIA[0-9A-Z]{16} AWS access-key-ID prefix.
GitHub Token gh[pousr]_[A-Za-z0-9]{36,} Classic / fine-grained / OAuth tokens (any prefix from the GH set).
GitLab Token glpat-[A-Za-z0-9_-]{20,} Personal access tokens.
Anthropic API Key sk-ant-[A-Za-z0-9_-]{40,} Console.anthropic.com keys.
OpenAI API Key sk-(?:proj-|live-)?[A-Za-z0-9]{40,} Including project + live variants.
JWT eyJ[A-Za-z0-9_-]{8,}\.eyJ[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{8,} Three-segment base64url-style JWT.
Stripe Live Key sk_live_[A-Za-z0-9]{24,} Production Stripe secret key prefix.
PEM Private Key -----BEGIN [A-Z ]*PRIVATE KEY----- Any PEM private-key header (RSA / EC / OPENSSH / generic).

Pattern names are sortable via the guard.SecretPatternNames() helper — useful if you want to audit the set from a test.

Adding your own patterns (guard.secret_patterns)

Since v1.7.0 (ADR-0024) you can extend the built-in list with your own credential regexes in config — useful for internal or company-specific token formats:

guard:
  secret_patterns:
    - name: Internal Token
      regex: 'INTERNAL-[A-Z0-9]{10}'
    - name: Acme API Key
      regex: 'acme_(live|test)_[A-Za-z0-9]{32}'
  • Additive only. User patterns run alongside the built-ins; they cannot disable or shadow one. A user entry whose name collides with a built-in is ignored (the built-in wins). To turn the scanner off entirely, use guard.secret_scan: false.
  • Each regex is a Go regexp (RE2) expression. An invalid pattern aborts the review before any provider call, naming the offending entry.
  • As with the built-ins, only the line number and pattern name are reported — never the matched text.
  • commitbrief config get guard.secret_patterns lists the configured names. config set is rejected for this key (it is a list of objects) — edit the config file directly, like providers.*.pricing.*.

What the user sees

When the scanner hits, stderr shows:

⚠  Possible secrets detected in diff (2 line(s)):
   line 42: AWS Access Key
   line 87: GitHub Token, OpenAI API Key

Send to LLM anyway?

  Yes  ▸ No        (←/→ to choose · Enter to confirm)

On a TTY the confirmation is an arrow-key Yes/No toggle (huh), pre-selected on No. Piped/non-TTY input falls back to the typed y/N line (a non-TTY run aborts unless --allow-secrets is set).

Important

The matched substring is never printed — only line numbers and pattern names. This keeps the secret out of stderr (and any CI log that captures it) even when the scanner fires.

Behavior matrix

Context Outcome
TTY, no --allow-secrets, no --yes Prompt; default no → abort with aborted: pre-send secret scanner.
TTY, --yes Prompt still shows. --yes does NOT bypass the scanner since v0.9.1 (UC-01).
Non-TTY, no --allow-secrets Abort with Aborted (non-interactive); pass --allow-secrets to override.
Any context, --allow-secrets Skip the scanner entirely.
guard.secret_scan: false in config Skip the scanner entirely (whole-binary opt-out).

The matched-line list is still printed to stderr in the --allow-secrets case — you opted in, but you still see what was flagged.

Disabling

Per-invocation

commitbrief --staged --allow-secrets

Per-config (whole binary)

commitbrief config set guard.secret_scan false

This is appropriate if you have a separate secrets-management layer (gitleaks, trufflehog, pre-commit hooks) doing the same job and the prompt is just noise.

What CommitBrief does NOT do here

  • It does NOT scan for entropy-based detections (high-entropy random-looking strings). Pure pattern matching only.
  • It does NOT ship built-in patterns for every service-specific token (Slack tokens, Twilio SIDs, etc.). Add them yourself via guard.secret_patterns, or open a PR to add a common one to the built-in set.
  • It does NOT auto-redact. The scanner only abort/prompts; it never rewrites the diff.
  • It does NOT scan removed-line history. A pre-existing checked-in key remains until you rotate it (which you should).

Beyond the diff: commitbrief leaks

Everything above describes the pre-send gate — it sees the added lines of the one diff about to be reviewed, and its job is to stop that send. It cannot tell you whether a credential is sitting in your working tree right now, or whether one was committed and later removed.

commitbrief leaks runs the same pattern set — built-ins plus your guard.secret_patterns, identical merge rules — over both: every tracked file, and the added lines of historical commits, each hit attributed to its commit, author and date.

commitbrief leaks                 # tracked files + the last 200 commits
commitbrief leaks --patterns      # the effective pattern set

The redaction rule is the same and matters more there, because leaks reads whole files: a finding carries a file, a line and the pattern names, never the matched text.

See also

Clone this wiki locally