All notable changes to this project will be documented in this file.
scan --stagedscans only the lines a commit adds- Baselines are auto-discovered from
.keywatch-baseline.json;--no-baseline-discoveryopts out update-baselineworkflow regenerates the baseline via a pull request
- Pre-commit hooks scan the staged diff instead of whole files
- Config discovery searches parent directories up to the repository root
- Hook messages abbreviate the home directory as
~
- CI scans this repository with KeyWatch and fails if the committed baseline has drifted
--prune-baselinerewrites the baseline from current findings, dropping entries for deleted files and rotated credentials; requires--update-baselineand a whole-tree scan, and prints what it dropped
- Reports redact matched text by default;
--show-secretsopts into raw values, and matches shorter than 8 characters are always described by length only - Reports summarise exclusions as a count plus a sample instead of listing every path, and report git-rendered binary files as
unscannablerather thanexcluded - Lockfiles (
Cargo.lock,package-lock.json,yarn.lock,go.sum, and other generated manifests) are excluded from scans by default
scan --git-historyapplies--exclude, skips the baseline file, and reports real file paths instead of a synthetic<git-history>key that no baseline could matchscan --stagedis not fooled bydiff.relative, which made git drop changes outside the current directory--outputfiles are readable only by their owner, including when the file already existed with wider permissions- Config is not trusted from a world-writable directory or file, so a
.keywatch.tomldropped in/tmpcannot weaken scans beneath it KEYWATCH_CONFIG_PATHis ignored in trusted mode whenever it points inside the tree being scanned, wherever the process runs from- Baseline suppression reports how many findings it hid, instead of applying silently
CreditCardDetectorrequires an issuer prefix and a valid Luhn checksum, instead of matching any 13-16 digit run; Discover's 644-649 and 65 ranges are coveredHighEntropyDetectorcould never fire (its 4.0 threshold is the ceiling for hex) and now runs, restricted to lines naming a credential- PKCS#8 private key headers (
BEGIN PRIVATE KEY,BEGIN ENCRYPTED PRIVATE KEY) are detected PhoneNumberDetectorneeds punctuation or a country code, so unix timestamps are not phone numbers- Detectors can require a structural check via
validate = "luhn" - Hooks use built-in detectors, so a
detectors.tomlcommitted to a scanned repository can no longer replace the detector set and disable its own scan - Files git renders as binary (including text marked
-diffin.gitattributes) are read from the index instead of being reported clean Base64Detectormatches from 28 characters, the length where entropy can actually separate base64 from identifiersscan --stagedno longer misses findings undercolor.ui = alwaysor custom diff prefixes- Non-UTF-8 files no longer abort a staged scan
- A malformed diff hunk header is reported instead of silently attributing its findings to line 0
- Diff paths that git quoted (names containing quotes or control characters) are unescaped before attribution
- The baseline file is no longer scanned as input to itself, including staged scans run from a subdirectory
GenericKeyValueDetectorandRandomStringno longer flag code identifiers (let payment_method_token = card_token, snake_case serde attributes)PasswordDetectorno longer flags$PWD:GenericKeyValueDetectorno longer flags bare CamelCase type paths (token: PaymentTokenData,)- Custom rules in
.keywatch.tomlsupportallowlist,keywords,entropyandvalidate, matching built-in detector definitions - Pre-push repository filters fail closed on Windows drive-path remotes instead of misparsing the drive letter as a host
- Chunked streaming scans no longer duplicate multiline matches that land inside the window overlap
- Files with invalid UTF-8 are decoded lossily and scanned instead of silently skipped; NUL-containing files are reported as
unscannable Finding'splugin_namefield is nowdetector_namein the code; the JSON report and baseline schema still emit/acceptplugin_nameCustomRule.descriptionwas parsed but never surfaced and has been dropped (configs carrying it keep parsing)- False-positive reductions in the built-in detectors: AWS's documentation example key, placeholder values (
changeme,your-api-key-here,replace-me-please), RFC 2606 example-domain emails and noreply conventions, fictional 555 phone numbers, npm/shield checksum prefixes, and non-Verhoeff 12-digit runs no longer report as Aadhaar --baselinenaming a missing file is an error instead of silently scanning with an empty baseline- Baseline files with an unknown format version are rejected instead of silently accepted
--update-baselinerefreshes the recorded line numbers of entries it already knows, and saved baselines end with a newline- SARIF report property order is deterministic
- Piping output to a closed reader no longer panics, including
hook installandinit; hook commands now report real output failures instead of discarding them
- Keyword matching uses a single Aho-Corasick pass per line: ~3x faster file scans, ~9x faster streams
- File scans stream line by line instead of reading whole files into memory
- ~2.5x faster file scans: one combined prefilter pass for the keywordless detectors, an ASCII fast path for line lowering, and a byte-histogram entropy check that no longer allocates per match
- GitHub Release asset publishing no longer fails when Action validation generates Python bytecode caches
- Public Rust APIs now return module-local typed errors instead of
Stringor boxed errors. This affects CLI validation, baseline, configuration, detector initialization, scanner, hook, andrun_cli()return types and requires a major-version release.
- CRITICAL severity support — findings can now be scored as Critical, High, Medium, or Low
- Baseline suppression —
scan --baseline <path>suppresses known findings from previous scans;--update-baselinewrites current findings to the baseline file - Inline suppression — add
# keywatch:ignoreor// keywatch:ignoreon a line to suppress findings - Per-detector allowlist — each detector in
detectors.tomlcan defineallowlistregex patterns to suppress false positives - Keyword prefilter — each detector can define
keywordsfor fast prefiltering before regex runs - Entropy threshold filtering — each detector can define
entropythreshold to reject low-entropy false positives - Parallel scanning — file scanning parallelized with rayon for multi-core speedup
- Stdin scanning —
scan --stdinreads content from stdin instead of files - Git history scanning —
scan --git-historyscansgit log -poutput for committed secrets - Cloud/monitoring/AI service detectors: Vercel, Netlify, Supabase, Datadog, New Relic, Sentry, PagerDuty, Anthropic, HuggingFace, Groq, Replicate, LangSmith
- GitHub Action — composite action (
action.yml) for CI/CD integration - Docker support — multi-stage Dockerfile with
--lockedflag, stripped binary, non-root user, and git installed for--git-historyscanning and hook installation - Public distribution verification — the root GitHub Action verifies release binary and detector checksums, while GHCR images publish semver, major, and latest tags with provenance
.dockerignorefor optimized Docker builds- Config file support —
.keywatch.tomlwith custom rules, detector overrides, and exclude patterns - SARIF 2.1.0 output —
--format sarifenables GitHub Code Scanning and SARIF viewer integration --configCLI flag — specify a custom path to.keywatch.toml- Pre-commit
language: system— generated hooks uselanguage: systemfor faster execution
get_severity_counts()now returns 4-tuple (Critical, High, Medium, Low) instead of 3-tuplerun_scan()accepts optionalconfigparameter for merging user configuration- Simplified distribution to a single shipped binary:
key-watch - Git hook installation now supports first-class global hooks via
core.hooksPath - Installation guidance is now cargo-first, with manual GitHub Releases setup documented step by step
- CLI moved from flat top-level flags to subcommands:
scan,hook install|uninstall,init, andverify-integrity - Local hook installation now resolves Git's hooks directory directly, improving worktree and submodule compatibility
exit-mode criticalnow fails on both HIGH and CRITICAL findings- Detector descriptions and comments cleaned up for minimal noise
- Release preparation now synchronizes the Action version with Cargo metadata and runs CI before publishing tags
- CI now validates Action shell behavior, checksum failures, release automation, and container smoke behavior
- Cargo-installed and standalone binaries now fall back to embedded detector rules when no external
detectors.tomlis available - CRITICAL severity was silently downgraded to LOW at runtime
- All clippy warnings resolved (
Defaultimpl, redundant closures, identity maps) - Public API unit tests moved to
tests/directory (only private API tests remain insrc/) - Baseline hash domain separator renamed from
SALTtoDOMAIN_SEPARATORfor clarity Severity::from_string()now trims whitespace from input before parsingscan_stream()chunk overlap fixed for accurate multiline detection on split chunks- Graceful error handling when
gitis not installed on the system action.ymlremovedeval "$CMD"pattern for securityaction.ymlremoved hardcoded GitHub authentication header.dockerignorenow preservesCargo.lockfor reproducible builds
- Duplicate Cargo binary wrappers for
keywatchandwatch scripts/install.shin favor of documentedcargo installand manual release-binary setup- ~1650 lines of redundant context-based detectors; kept only prefix-based detectors plus GenericKeyValueDetector
- README architecture documentation now uses three source-controlled D2 diagrams with generated SVGs for CLI modules and adapters, the scan pipeline, and detector/configuration trust boundaries
- Binary aliases:
keywatch,watch(in addition tokey-watch) - Exit code modes:
--exit-mode always|critical|strict - Binary integrity verification:
--verify-integrity - Repository controls:
--allowed-repos,--blocked-repos - Multiple file scanning:
--file file1.txt --file file2.txt - Indian ID detectors: Aadhaar, Voter ID (EPIC), PAN Card, ABHA Health ID
- Shell injection protection in generated hooks
- Non-UTF8 file handling (graceful skip)
- Simplified README (~60 lines)
- User-friendly output by default (summary, not JSON)
- Default exit mode: strict
- Source builds now require Rust 1.85+ (edition 2024)
- Portable detector loading (exe-relative path)
- Filenames with spaces handling
- Hook repo allow/block rules are now enforced
- Exclude globs now work correctly for directory scans
- Runtime errors now use exit code
2instead of1 - Hook subshell bug: exit now correctly blocks commits/pushes
- Hook detectors.toml check: removed hard CWD requirement (exe-relative works)
- Hook error messages now use correct binary name variable
- Duplicate file paths now deduplicated before scanning
- Legacy
hooks/keywatch.sh .pre-commit-config.yaml
- Initial release
- File/directory scanning
- Verbose JSON output
- Pre-commit/pre-push hooks