Secret scanning

Secret scanning inspects each file's content for likely credentials before output. See the how-to for the task-oriented walk through; this page is the exhaustive surface.

Flags

FlagValuesEffect
--secret-scanoff, warn, redact, blockWhat to do on a finding (default warn)
--secret-scan-allow <FRAGMENT>path substring, repeatableSkip files whose path contains the fragment
--scan-threads <N>integer, 0 = defaultThreads for the scan (see tuning)
--dfa-cache-mb <MB>integer, 0 = defaultPer-thread regex DFA cache (see tuning)

Policies

PolicyContentFindingsExit
offunchangednone (no scan)0
warnunchangedreported on stderr0
redactsecret โ†’ [REDACTED: <rule>]reported on stderr0
blockfile droppedreported in abort messagenon-zero if any found

redact runs before token counting, so redaction shrinks the reported token total. Previews in reports show only the first few characters plus a length โ€” the full secret is never printed or logged.

Detection rules

gnaw scans with the gitleaks ruleset (MIT-licensed), vendored into the binary so scanning is offline and reproducible โ€” no network calls, no runtime download. Each rule is a regex plus, for most, a per-rule Shannon-entropy floor: a match below the floor (a low-entropy documentation example, say) is rejected, which is what keeps placeholder keys out of the report.

The ruleset is large โ€” a few hundred rules โ€” and covers the credential shapes you'd expect: cloud providers (AWS, GCP, Azure), source forges (GitHub, GitLab, Bitbucket), messaging and payments (Slack, Stripe, Twilio, SendGrid), AI vendors (OpenAI, Anthropic), package registries, PEM private-key blocks, JWTs, and a family of generic high-entropy assignment rules for key/secret/token/ password-named fields.

gnaw adapts gitleaks' Go (RE2) patterns to Rust's regex engine. The two are close relatives, so the vast majority compile verbatim; a rule that uses a construct Rust's engine rejects is skipped rather than failing the whole ruleset, and the compile rate is checked whenever the vendored ruleset is refreshed.

Where gnaw deliberately differs from gitleaks

gnaw is not a byte-for-byte gitleaks. A few targeted overrides live in gnaw's code (not the vendored TOML, so they survive ruleset updates), each trading a specific false positive or false negative:

Recovered credential families. gitleaks' generic-api-key rule treats the whole match as the secret and suppresses by variable name, which silently misses access_token / auth_token / password / client_secret assignments. gnaw scopes the rule to the assigned value instead, recovering those families. The cost is that a high-entropy hash sitting in a credential-named variable could look like a secret โ€” so gnaw pairs the change with a value-shape allowlist that suppresses md5/sha/UUID/SRI-shaped values. The accepted residual: a real secret that is itself exactly hash- or UUID-shaped gets allowlisted.

Private keys. gnaw replaces the vendored private-key pattern with one that requires a full -----END โ€ฆ PRIVATE KEY----- marker to close and at least one full-length base64 run in the body. This does two things the stock pattern doesn't: it stops a short/truncated placeholder block from lazily consuming a following real key's BEGIN marker (a detection bypass), and it keeps documentation placeholders โ€” which have no real key material โ€” out of the report. Modern single-line keys (Ed25519 and similar) are detected.

The practical upshot: gnaw aims to be more precise on placeholders and more thorough on real credentials than the stock ruleset, not merely equal to it. Where it diverges, it diverges on purpose and in code you can read.

Tuning

Secret scanning is the memory- and CPU-heavy part of a run. Two flags let you trade throughput against footprint; both default to a value gnaw picks for you, and most users never touch them.

Flag0 (default) meansRaise toLower to
--scan-threadsauto (a capped share of your cores)scan faster on a big hostcap memory
--dfa-cache-mbbuilt-in default(rarely needed)cap per-thread cache

The scan runs on a bounded thread pool so it doesn't crowd out the rest of the pipeline, and rule regexes compile lazily โ€” a rule whose keyword never appears in your content is never compiled โ€” so scanning a repo that uses few credential shapes stays cheap.

Ordering in the pipeline

Scanning runs after compression and before token counting. So a secret inside a function body that compression already stripped never reaches the scanner, and when you redact, the reported token total reflects the scrubbed output.

Path allowlisting

--secret-scan-allow <FRAGMENT> skips any file whose path contains the given substring; repeat the flag for several fragments. With no fragments supplied, gnaw uses a built-in set aimed at test and fixture directories, so intentional fake keys in your test suite don't light up the report. Supplying your own fragments replaces the built-in set.

.gnawconfig keys

KeyTypeDefault
secret_scan"off" / "warn" / "redact" / "block""warn"
secret_scan_allow_pathsarray of path-substring stringsbuilt-in test set
secret_scan = "redact"
secret_scan_allow_paths = ["tests/", "fixtures/"]

Resolution order is CLI flag โ†’ .gnawconfig โ†’ built-in default.