A fast secret scanner for files and directories.
cargo install key-watch
key-watch --version
# Enable aliases for your current shell session
eval "$(key-watch init bash)"To make aliases persistent, add the init line to your shell config file:
# bash
echo 'eval "$(key-watch init bash)"' >> ~/.bashrc
# zsh
echo 'eval "$(key-watch init zsh)"' >> ~/.zshrc- Download the correct binary for your OS/architecture from GitHub Releases.
- Move it to a directory on your
PATH, for example~/.local/bin. - Make it executable.
- Verify it runs.
- Enable aliases with
init.
mkdir -p ~/.local/bin
mv ~/Downloads/key-watch ~/.local/bin/key-watch
chmod +x ~/.local/bin/key-watch
~/.local/bin/key-watch --version
# Enable aliases for current shell session
eval "$(~/.local/bin/key-watch init bash)"Requires Rust 1.85+ (edition 2024) when building from source.
The canonical command is key-watch.
keywatch and kw are optional shell aliases exposed via key-watch init ....
Use the root Action from a public workflow. The major tag follows compatible 2.x releases; pin an exact release tag or commit SHA when immutable dependencies are required.
name: Secret scan
on:
pull_request:
push:
permissions:
contents: read
jobs:
keywatch:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
- id: keywatch
uses: pixincreate/KeyWatch@v2
with:
paths: "."
exit-mode: strictThe Action installs the synchronized KeyWatch release, verifies SHA-256 checksums for the binary and detectors.toml, disables repository detector discovery, and writes a JSON report. It supports Linux x64 and macOS x64/arm64 runners; Windows runners are not supported.
| Input | Default | Purpose |
|---|---|---|
version |
Action release version | Exact KeyWatch release to install |
paths |
. |
Space-separated paths or globs to scan |
args |
empty | Additional scanner arguments that do not override Action-managed options |
exit-mode |
strict |
strict, critical, or always |
output |
temporary report | JSON report path |
config |
empty | Explicit trusted .keywatch.toml path |
verbose |
false |
Deprecated; enabling it is rejected to prevent secret disclosure in logs |
The findings-count and exit-code outputs are available as ${{ steps.keywatch.outputs['findings-count'] }} and ${{ steps.keywatch.outputs['exit-code'] }}.
The GitHub Container Registry image is a separate distribution channel for Linux x64 environments:
docker pull ghcr.io/pixincreate/keywatch:2
docker run --rm \
--volume "$PWD:/workspace:ro" \
--workdir /workspace \
ghcr.io/pixincreate/keywatch:2 scan .Images are published as x.y.z, x.y, x, and latest, with build provenance attached. Exact semver tags are the reproducible choice. After the first publication, a repository owner must make the GHCR package public in the package settings to allow anonymous pulls; no separate GHCR account is required. The image runs as a non-root user and uses the image-owned detector configuration at /etc/keywatch/detectors.toml.
cargo uninstall key-watchIf you added aliases to your shell config, remove the init line you added earlier, for example:
# bash
sed -i.bak '/key-watch init bash/d' ~/.bashrc
# zsh
sed -i.bak '/key-watch init zsh/d' ~/.zshrc- Remove the
key-watchbinary from yourPATHdirectory. - Remove any shell init line you added for aliases.
- Restart your shell or reload your shell config.
rm -f ~/.local/bin/key-watch
# If you added aliases for the current shell config, remove that line manually
# then reload your shell config, for example:
source ~/.bashrc# Scan a file
key-watch scan secrets.txt
# Scan a directory
key-watch scan .
# Scan from stdin
cat secrets.txt | key-watch scan --stdin
# Scan git history for committed secrets
key-watch scan --git-history
# Verbose output (JSON)
key-watch scan secrets.txt --verbose
# Install git hook
key-watch hook install pre-commit
key-watch hook install pre-push
# Remove git hook
key-watch hook uninstall pre-commit
key-watch hook uninstall pre-push
# Install git hook globally via core.hooksPath
key-watch hook install pre-commit --global
key-watch hook install pre-push --global
# Remove global hook
key-watch hook uninstall pre-commit --global
key-watch hook uninstall pre-push --global
# Print shell aliases
eval "$(key-watch init bash)"
# Verify binary integrity
key-watch verify-integrityscan <path>...- Scan one or more files or directoriesscan --config <path>- Load configuration from an explicit.keywatch.tomlpathscan --no-config-discovery- Ignore discovered repository config unless--configis explicitscan --format <json|sarif>- Choose the report format written to stdout or the output filescan --stdin- Read content from stdin instead of filesscan --git-history- Scan git history (git log -p) for committed secretsscan --output <path>- Save report to filescan --verbose- Print full JSON outputscan --exclude <patterns>- Comma-separated glob patterns to excludescan --exit-mode <mode>- Exit behavior:always(always pass),critical(fail on HIGH/CRITICAL only),strict(fail on any finding, default)scan --baseline <path>- Suppress known findings from a previous scanscan --update-baseline- Update baseline with current findings (requires--baseline)hook install <pre-commit|pre-push> [--global]- Install a git hookhook uninstall <pre-commit|pre-push> [--global]- Remove a git hookhook install pre-push --allowed-repos <urls>- Whitelist repos for pre-push hookshook install pre-push --blocked-repos <urls>- Block repos for pre-push hookshook install pre-commit --exclude <patterns>- Exclude patterns for pre-commit scansinit <shell>- Print shell aliases forkeywatchandkwverify-integrity- Check binary hasn't been tampered with
key-watchis the only shipped binary.keywatchandkware optional aliases.key-watch init bash|zsh|fish|posixprints shell aliases you can eval in your shell.watchis intentionally not used, to avoid colliding with the standard Unixwatchcommand.
Use baselines to suppress known findings on subsequent scans:
# First scan: create a baseline
key-watch scan . --baseline .keywatch.baseline --update-baseline
# Future scans: only report NEW findings
key-watch scan . --baseline .keywatch.baselineAdd keywatch:ignore to suppress a finding on a specific line:
password = 'known-test-password' # keywatch:ignore| Code | Meaning |
|---|---|
| 0 | No secrets found (or scan --exit-mode always) |
| 1 | Secret found (in strict/critical mode) |
| 2 | Runtime/configuration error |
- Repos: All allowed (no restrictions)
- Exit mode: strict (fail on any finding)
hook install pre-commit|pre-pushinstalls a repo-local hook into.git/hooks/hook uninstall pre-commit|pre-pushremoves a KeyWatch hook from the same targethook install ... --globalinstalls into Git's global hooks directoryhook uninstall ... --globalremoves the hook from Git's global hooks directory- Local hook paths are resolved via
git rev-parse --git-path hooks, so installs work in worktrees and submodules too - If
core.hooksPathis already configured, KeyWatch installs into that directory - Otherwise KeyWatch creates a managed hooks directory and configures
git config --global core.hooksPath - KeyWatch refuses to overwrite a non-KeyWatch global hook file
- KeyWatch also refuses to remove a non-KeyWatch global hook file
KeyWatch is a single Rust CLI organized as a modular monolith. main.rs owns startup and maps validation, configuration, or runtime failures to exit code 2. run_cli() validates and routes commands, while the scan coordinator currently terminates successful scan execution with code 0 or 1. Focused modules own detector loading, repository policy, scanning, baselines, reports, hooks, and filesystem or process adapters.
The green boxes are internal modules, blue boxes mark entry or output boundaries, and yellow boxes are external runtime or distribution adapters. Rust hook management renders and installs scripts; the shell templates are separate runtime adapters that invoke key-watch scan.
Path scans collect and process files in parallel, while stdin and git history use overlapping stream chunks. Baseline updates short-circuit normal report generation. Scan results exit with code 0 or 1; validation, configuration, and runtime failures are mapped to code 2 at the process boundary.
Detector definitions and repository policy are separate configuration systems. External detector sources retain precedence, with compiled-in rules as the final fallback. Trusted scans skip repository-owned discovery but still honor explicit configuration and non-repository detector sources.
- Detector — a named rule: regex, finding type, severity, optional keywords for pre-filtering, an entropy threshold, and an allowlist.
- Finding — one detected secret: file path, line number, finding type, severity, matched content, and the detector that produced it.
- Severity —
Critical,High,Medium,Low. - KeywatchConfig — parsed
.keywatch.toml: custom rules, per-detector overrides, and exclude patterns. - Baseline — versioned collection of fingerprint entries; filters out already-known findings.
- ScanMetadata — files scanned, total lines, and excluded files, reported alongside findings.
The canonical diagram sources are in docs/architecture/*.d2. Run scripts/render-diagrams.sh render with D2 v0.7.1 after editing them, or scripts/render-diagrams.sh check to detect stale SVGs.
cargo build --release
cargo test
cargo fmt
cargo clippy