test: cover the behaviour that was shipping unverified - #90
Merged
Conversation
pixincreate
force-pushed
the
chore/cleanup
branch
from
August 26, 2026 17:32
93353a1 to
93b4574
Compare
pixincreate
force-pushed
the
test/ci-hardening
branch
from
August 26, 2026 17:32
9d2b52b to
191c9b9
Compare
pixincreate
added a commit
that referenced
this pull request
Sep 1, 2026
git_available() grew into an assertion in #90 but still returned a bool, leaving 23 'if !git_available() { return Ok(()) }' guards as dead code that read like the tests could still skip. require_git() replaces it: same loud failure, no dead branches, and the git-backed tests say so.
pixincreate
marked this pull request as ready for review
September 1, 2026 17:57
pixincreate
force-pushed
the
chore/cleanup
branch
from
September 2, 2026 16:45
93b4574 to
031ebe5
Compare
git_available() turned twelve tests into silent no-ops if git were missing, including every --staged and baseline integration test; git is a hard dependency of those modes, so it now fails loudly. The staged scan's git-config hardening had no test at all, despite being the fix for a bypass: deleting any of the overrides made the scan report clean or attribute findings to a mangled path, and nothing failed. One test now sets all five hostile settings at once and asserts the path and line survive. Also newly covered: the scanner exits 2 outside a repository, which is what makes the hook fail closed; path operands actually narrow the staged diff; a config at the repository root is discovered, which depends on candidate lookup preceding the .git stop check; and the prefilter selects both owners of overlapping keywords, without which Stripe detection would silently stop running behind Adyen's shorter keyword. CI never ran KeyWatch against itself, so a committed secret or a stale baseline would ship green.
…ust cwd The trusted-mode guard compared the env-supplied detector config against the process's current directory, so 'key-watch scan --no-config-discovery <repo>' run from anywhere else still honored a detector set that the scanned repository had planted inside itself. Trusted initialisation now receives the untrusted roots: the enclosing repository root for git-backed modes, each scan target's enclosing repository root for file scans, and always the current directory. Discovery also refuses a world-writable config file, not only one sitting in a world-writable directory: a 0666 file can be rewritten in place even inside a 0755 directory. The same refusal now covers discovered baselines, which a world-writable attacker could pad with suppression entries.
Staged and history diffs emit repository-root-relative paths no matter where the process runs, but baseline self-exclusion canonicalized them against the current directory. Running 'scan --staged' from a subdirectory therefore re-scanned the staged baseline file and flagged its own stored hashes — the exact failure the self-exclusion exists to prevent. Every mode now anchors resolution at the scanned tree's root.
…y entries --prune-baseline rebuilt the baseline from whatever the scan found, so a staged or path-narrowed scan deleted the entries for everything it never scanned, and a bare --prune-baseline was silently ignored. It now requires --update-baseline, conflicts with the partial-scan modes, prints how many entries it dropped, and warns when explicit paths narrow the scan.
Redaction was controlled by a process-global atomic that serialization consulted invisibly: the flag appeared in no signature, and any future test that set it would leak into every other report test running in parallel. create_report now takes show_secrets and redacts eagerly. Redaction also stops showing a prefix for matches shorter than eight characters, where four visible characters would reveal most or all of the secret.
The 0600 mode only applied at file creation, so re-running into an existing world-readable report kept it world-readable while writing potentially secret-bearing content into it. Permissions are now forced on the open file handle before the write.
Files git renders as binary were never seen by the scanner, but history mode listed them under excluded_files and staged mode listed unopenable blobs there too — reading like operator-requested skips. After the count-plus-sample change those entries are invisible, so the gap the report describes no longer matched reality. A new unscannable field carries what could not be read; excluded keeps what was deliberately skipped.
A hunk header the parser could not read silently attributed its findings to line 0; the fallback stays (scanning beats skipping) but now says so on stderr. Quoted diff paths — git still C-quotes names containing quotes or control characters even with core.quotePath=false — are unescaped before attribution, including inside binary markers where a quoted name containing ' and ' previously split the pair. Findings are also sorted exactly once per mode: the staged parser sorted its own output and the staged branch sorted again after appending blob findings, and history relied on the parser's sort.
Lockfile checksums are the single largest source of noise in reports and baselines — 91 of this repository's 231 baseline entries were Cargo.lock checksums. They hold checksums and resolved URLs, never credentials, so every filesystem-backed mode now skips them by basename, matching gitleaks. The committed baseline is regenerated without them; this also refreshes line numbers that had drifted (the self-scan job moved the ci.yml finding), which the CI drift check would otherwise have flagged.
…ccept gate The credit-card pattern covered 6011 but not Discover's other prefixes, so a 65-prefixed card passed Luhn and went unreported — a gap in a detector set whose point is accuracy. The allowlist/entropy/validator chain existed three times: in both scanner loops and again in the detector tests' reported_by helper, free to drift apart the next time a gate is added. Detector::accepts_match is now that chain, used everywhere.
The broken-pipe fix made hook commands swallow every stdout error with 'let _ =', while the scan path propagates real I/O failures — two policies over the same helper. emit_line already returns Ok for a closed pipe, so propagating the rest loses nothing and hook install/uninstall now surface disk-level failures. read_global_hooks_path also drops its guard-arm match for a plain if/else.
Every ScanArgs field addition cost 32 mechanical one-line edits across the test suites (show_secrets and prune_baseline each did exactly that). The 32 hand-written literals now name only the fields under test and fill the rest from Default.
git_available() grew into an assertion in #90 but still returned a bool, leaving 23 'if !git_available() { return Ok(()) }' guards as dead code that read like the tests could still skip. require_git() replaces it: same loud failure, no dead branches, and the git-backed tests say so.
pixincreate
force-pushed
the
test/ci-hardening
branch
from
September 2, 2026 16:48
9289316 to
606fa8b
Compare
The credit-card detector's new 644-649/65 prefixes match two test fixtures added with them; the committed baseline predates that change and the self-scan job (correctly) refused to pass with un-baselined findings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stacked on #89 — top of the stack.
Summary
Closes the coverage gaps where a regression would have shipped green, and makes CI run KeyWatch against itself.
Changes
git_available()silently skipped 12 tests — every--stagedand baseline integration test — if git were missing. Git is a hard dependency of those modes, so it now fails loudly instead of reporting green while covering nothing.color.ui=always,diff.mnemonicPrefix,diff.noprefix,core.quotePathanddiff.relativesimultaneously and asserts the path and line survive..gitstop check — both prior tests passed either way); and the prefilter selects both owners of overlapping keywords, without which Stripe detection silently stops running behind Adyen's shortersk_.self-scanjob scans the repo, fails if the committed baseline has drifted, and checks the framework hook entry still usesscan --staged.Tests
192 pass (was 177 at the base of this stack), clippy and fmt clean.