keyhog scans source trees, git history, Docker images, GitHub/GitLab/Bitbucket
repository collections, S3/GCS/Azure Blob buckets, and running systems for leaked credentials. 923 embedded detectors,
decode-through (base64/hex/url/protobuf), confidence scoring, and SARIF output
without hand-written runtime configuration. After verified-install calibration,
keyhog scan . works with the canonical defaults; a source-built multi-backend
binary first runs keyhog calibrate-autoroute.
Start in 60 seconds
# Linux / macOS: install, then scan the current tree
|
That is the whole first run. From here, keyhog scans a lot more than a directory. Every use case is a single copy-paste command in the Recipes cookbook (mirrored below in Quickstart): whole GitHub/GitLab/Bitbucket orgs, git history, Docker images, S3/GCS/Azure buckets, live URLs, and a full machine sweep.
Add it to your CI (one workflow file)
# .github/workflows/keyhog.yml
name: keyhog
on:
permissions:
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: santhreal/keyhog@v0
with:
Release refs authenticate the complete binary and GPU literal bundle with
minisign and SHA-256. Branch/SHA refs skip release lookup and build the
checked-out source. With no explicit diagnostic backend, the Action visibly
calibrates the runner before its default auto scan. The job summary reports
measured duration; cost varies with the runner, cache, configuration, and
repository. Findings auto-upload to GitHub code-scanning as SARIF. Adopt
without breaking an existing tree by committing a baseline
(keyhog scan --create-baseline .keyhog-baseline.json) so the action
fails only on NEW secrets.
Release-tag behavior is fail-closed: exact release tags and the floating
@v0 tag require complete, verifiable release assets. A missing or
unverifiable asset never silently falls back to building different source;
only branch/SHA refs build from source.
For lean CI source builds, disable default features and select the CI profile:
This profile has no Hyperscan dependency, wgpu/Vulkan probe, or libstdc++ link.
Native TLS still needs the platform's TLS build prerequisites. On Debian/Ubuntu,
install libssl-dev and pkg-config. The profile retains the same embedded
detector and ML/entropy/decode/multiline data paths. Use it in self-built CI
images where binary size
or container cold-start matters; the prebuilt installer above stays the
default for a turnkey single-binary download.
GitLab CI, CircleCI, Drone, BuildKite, Jenkins, pre-commit, Husky, and lefthook recipes: integration recipes.
Protect local commits with keyhog hook install. The
pre-commit guide owns staged-content,
hook-replacement, bypass, and removal semantics. The
CI guide owns the maintained workflows, baseline
adoption, report retention, and exit handling.
How it works
KeyHog compiles its 923 detectors into a shared trigger/extraction plan, uses Hyperscan when that feature is present, decodes nested encodings before matching, and can apply explicit per-detector Bayesian Beta(α,β) confidence calibration. Hardware acceleration is an explicit backend selection layer; every selected backend must preserve the same detector ids and findings contract:
| Layer / Backend | When | How |
|---|---|---|
simdsieve prefilter |
AVX-512 / AVX2 / NEON | Layer 1: skims every file for 12 high-value literal prefixes in one SIMD pass: AWS AKIA/ASIA, GitHub ghp_, OpenAI sk-proj-, Slack xoxb-/xoxp-, SendGrid SG., Square sq0csp-, and Stripe sk_live_/sk_test_/rk_live_/rk_test_ |
gpu-cuda-region-presence |
executable CUDA peer + persisted calibration proof | VYRE literal-set region-presence through CUDA, followed by the shared CPU validation tail |
gpu-wgpu-region-presence |
executable WGPU peer + persisted calibration proof | VYRE literal-set region-presence through WGPU, followed by the shared CPU validation tail |
simd-regex |
Hyperscan compiled and live | parallel Hyperscan trigger scan plus full-regex extraction; portable builds do not expose this backend and report cpu-fallback instead |
cpu-fallback |
portable build or explicit CPU selection | Aho-Corasick prefix + Rust regex extraction |
Autoroute
KeyHog autoroute measures every eligible backend with phase-two localization on
and off, then persists the fastest parity-checked route for the exact binary,
host, resolved policy, and workload class. It is not a hardware heuristic or
fallback hierarchy. A missing, stale, invalid, or quarantined decision is never
called autoroute: KeyHog warns, scans every byte through the scalar correctness
oracle, and reports complete_after_recovery with the recalibration command.
Install performs the visible calibration. To recalibrate an installed binary,
run keyhog calibrate-autoroute; inspect evidence with
keyhog backend --autoroute. Explicit --backend values are diagnostic and
benchmark overrides, not autoroute proof. Single-backend portable builds do
not need a routing cache.
If an automatically selected accelerated backend faults, KeyHog warns and
replays the same stable input through the fastest remaining measured-correct
peer. GPU recovery retains completed shards and scans only exact unprocessed
ranges. KeyHog reports complete_after_recovery. The affected workload route is
quarantined in a bounded runtime-health artifact separate from calibration
timings, so a restart cannot retry it. Successful recalibration clears only the
repaired workload identities. Explicit or required backends remain hard
contracts and are never substituted.
The complete parity contract, workload identity, GPU/Hyperscan behavior, daemon semantics, cache lifecycle, and troubleshooting matrix live in the autoroute reference.
Full documentation: santhreal.github.io/keyhog - install, first scan, output formats, detection internals, suppressions, verification, pre-commit + CI integration, CLI reference, autoroute, exit codes, env vars, and contributing. Source under docs/.
Install
The canonical installer endpoints are short and stable:
# Linux / macOS
|
# Windows PowerShell
|
The installer verifies the selected release artifact before replacing a binary. For an installation that authenticates the installer script itself before execution, use the pinned signed flow below.
The signed Linux release binary needs the Hyperscan runtime and the installer
needs minisign before it can verify anything. On Debian/Ubuntu, install both
with sudo apt-get update && sudo apt-get install -y --no-install-recommends curl libhyperscan5 minisign;
on macOS, run brew install minisign (the macOS release asset is portable and
does not need Hyperscan).
# Linux / macOS, pinned and authenticated before execution
TAG=v0.5.42
BASE="https://github.com/santhreal/keyhog/releases/download/"
PUB='RWTPnJ/p6xVJ3TJIxr+ZVHMD/MTHWZhsdE38Go/oD3DYBoi4bePR55go'
KEYHOG_VERSION=""
# Windows uses the same signed, versioned flow. See the install guide.
# From source - Linux (install libhyperscan-dev + libssl-dev + pkg-config first)
&&
# From source - macOS Hyperscan path (Homebrew)
# Portable scanner build - Windows or a host without Hyperscan/Vectorscan
# (no Hyperscan or GPU stack; native TLS build prerequisites still apply)
The signed versioned installer is the recommended path. It authenticates the installer before execution, then selects and verifies the platform asset. See the install guide for PowerShell and checksum commands. Download and build time depend on the network, host, and cache. For a source build, note that the default features link Hyperscan/Vectorscan. Linux source builds also require
libssl-devandpkg-configfor native TLS. Linux useslibhyperscan-dev; macOS source builds use Homebrewvectorscan. On Windows or a host without either library, build with--no-default-features --features portablefor the pure Rust CPU scanner path with all portable scanner data features. Network source and verification features still use the platform native TLS dependency.
Release installers support Linux x86_64, macOS (Intel + Apple Silicon),
and Windows x86_64. Linux and Windows arm64 release assets are not
produced. Verified installers calibrate multi-backend builds before enabling
default automatic scans; a source build must run keyhog calibrate-autoroute
first or use an explicit diagnostic backend.
The installer selects one asset per OS/architecture. The Linux x86_64 binary
contains Hyperscan plus both VYRE CUDA and WGPU drivers; CUDA/NVRTC are loaded
dynamically, so the same binary works on NVIDIA, other compatible GPUs, and
CPU-only hosts without a build-time CUDA toolkit. Runtime probing reports which
engines are usable, while persisted autoroute evidence selects the
fastest measured-correct engine for each workload. macOS and Windows release
assets are portable no-system-library builds without Hyperscan or GPU drivers.
Each download is verified before it can replace your binary:
the installer checks the release's
minisign signature against keyhog's pinned public key and fails closed
(refuses to install, touching nothing) if the signature is missing, wrong, or
minisign itself is not installed - in which case it prints the one-line install
command for your OS (sudo apt-get install minisign, brew install minisign,
winget install -e --id jedisct1.minisign). It then SHA256-verifies the binary
against the release-side checksum file. The offline --from-file path also
verifies sibling .minisig files when present and rejects invalid signatures.
Passing --insecure can accept missing proof, but it never accepts a mismatch.
Pin a version with KEYHOG_VERSION=v0.5.42. Change the install dir with
--install-dir=/usr/local/bin. Runtime backend policy belongs to
keyhog scan --backend ..., [system].gpu, and autoroute calibration, not the
installer asset name.
Three diagnostic modes ship with the same script:
For an interactive install (post-install wizard for PATH, shell completions, and a git pre-commit hook), reuse the authenticated versioned installer:
KEYHOG_VERSION=""
Daemon mode is Unix only. Everything else works identically on Windows.
Keep keyhog healthy and up to date
Once installed, keyhog maintains itself - the install script is only needed for the first install:
keyhog doctor: host probe, install/PATH resolution, and an end-to-end scan
self-test. On a usable physical-GPU host it additionally checks the production
GPU scan path, GPU literal set, and GPU MoE shader against the CPU reference;
those GPU checks are skipped on hosts without an eligible accelerator:
keyhog doctor reuses the scanner's own hardware probe and runs a real
end-to-end self-test - it plants a synthetic secret and confirms the
binary detects it - so it is the authoritative "will keyhog work here?"
check (the installer runs it automatically after install). update and
repair download the release binary and GPU-literal sidecar over HTTPS,
verify both minisign signatures against keyhog's embedded public key, require
both release-manifest SHA-256 checksums to match, and install them as one
rollback-protected maintenance operation. A tampered, mismatched, or unsafe
archive is refused. On a healthy host keyhog update is the one-command upgrade
path. Implicit update/repair resolution ignores drafts and prereleases and
requires the complete signed host bundle; pass --version <TAG> to select an
exact published tag, including a prerelease. Network responses are bounded and
timed out before any installed file is changed.
keyhog backend --self-test --json is the machine-readable GPU health
gate for self-hosted runners. It exits 4 when the production GPU
region-presence path fails and emits stable ok, status, exit_code,
healthy_gpu_backends, route_selection, and per-probe fields for CI health
gates. route_selection is not_measured because a self-test proves
correctness, not comparative speed. Use keyhog backend --autoroute to inspect
the measured route.
On a host without an eligible physical GPU it returns one gpu_adapter probe
with status skip and exits 0; add --require-gpu to make absence a failed
health gate (exit 4).
Quickstart
KEYHOG_GITHUB_TOKEN="" KEYHOG_GITLAB_TOKEN="" KEYHOG_BITBUCKET_USERNAME="" KEYHOG_BITBUCKET_TOKEN="" \
Filter, format, gate:
One scan, every CI/SIEM dialect: text · json · json-envelope · jsonl · jsonl-envelope · sarif · csv · html · junit · github-annotations · gitlab-sast, all from the same engine:
Exit codes: 0 clean, 1 findings above the severity floor, 2 user error
(bad path, bad config, unsupported flag), 3 system error or detector-corpus
audit failure, 4 backend --self-test failed, 10 live credentials found
(requires --verify), 11 scanner panic (thread panicked mid-scan), 12 required GPU
unavailable, 13 requested source failed or input coverage was incomplete. Matches
keyhog --help.
What it catches
923 embedded detectors with detector-owned offline validation and companions:
- Cloud providers: AWS (access key + secret + STS verification), Azure (subscription key, storage account key, SAS), GCP (service account, API key), Cloudflare, Heroku, Vercel, Supabase.
- Payment processors: Stripe, Braintree, Razorpay, Paddle, Plaid, Square, and PayPal, with detector-owned checks and optional or required companions. A Razorpay key secret requires its nearby key ID.
- Source forges: GitHub PATs (with CRC32 checksum), GitLab tokens, Bitbucket app passwords, npm tokens (with checksum), Gitea / Forgejo / Codeberg.
- Auth / SSO: Okta, Auth0, Clerk, JumpCloud, Kinde.
- Comms: Slack, Discord, Twilio, SendGrid, Postmark, Mailgun, Resend, Loops.
- AI / ML: OpenAI (sk-/sk-proj-), Anthropic, Google AI Studio,
Cohere, Mistral, HuggingFace, Replicate. HuggingFace organization
credentials include both the current
hf_form and legacyapi_org_tokens. - Password managers: 1Password account secret keys (
A3-followed by five or six segmented uppercase alpha-numeric components). - Databases: Postgres connection strings, MongoDB Atlas, Supabase service-role, PlanetScale, Neon, Turso, MySQL, Redis URLs.
- Generic + entropy discovery:
API_KEY=<high-entropy-blob>catches credentials with no named detector, gated by per-context entropy thresholds + ML scoring. - Cryptographic material: RSA / EC / SSH private keys, PGP private blocks, JWT signing secrets.
Each detector ships as a TOML file (data, not code): service metadata, regex patterns, keywords, offline validators, entropy and ML policy, companion fields, and verification handler. Adding a new detector is a single reviewable TOML change; the contributor guide walks through it.
keyhog explain <id> dumps any detector's full spec: patterns, keywords,
verification endpoint, plus a service-keyed rotation and step-by-step
remediation guide, so a finding is never a black box:
Browse detector authoring and inspection in the
detector reference, or query the installed corpus with
keyhog detectors --search <term> --verbose.
Why higher recall, fewer false positives
- Decode-through scanning. Kubernetes
Secretmanifests, Jupyter notebooks, JWT payloads, base64-wrapped envs, Helm values, and docker-configauth:blobs. The structured preprocessor treats balanced Helm actions as inert render-time values and closes missing Jupyter delimiters at end of file, so literal bytes and complete code cells remain covered. It decodes structured values in place and feeds every downstream detector the plaintext. Detectors do not each need to re-implement decoding. Decode-enabled scans also recover side-effect-free JavaScript byte-array XOR and AES-256-CBC expressions when all recovery material is embedded, including strict CryptoJS/OpenSSL salted passphrase wrappers. KeyHog never executes the source. - Multiline reassembly.
"sk-proj-" + \continuation in JavaScript, YAML multi-line strings, Makefile backslash-continuation, Helm / Jinja templated outputs, all reassembled before regex matching. - Companion validation. Required companions gate high-noise detectors. A Twilio API key without its API secret is skipped. Optional companions enrich confidence or verification. AWS access-key detection does not require its secret, but the secret is needed for live verification.
- Confidence scoring. Every finding carries a
[0.0, 1.0]score derived from Shannon entropy, surrounding context, companion match, detector-owned offline proof (GitHub/npm CRC32 and PyPI payload decoding), structural evidence, and a small ML classifier (~30k params). Default threshold0.40(the canonicalScanConfig::default()floor; same as the--min-confidencedefault and the[scan].min_confidenceexample below) filters low-quality matches without hiding real secrets. - Bayesian per-detector calibration.
keyhog calibrate --fp generic-api-keywrites a Beta(α,β) posterior. Scans use it only when--calibration-cacheor[system].calibration_cachepoints at that file, so confidence tuning is explicit and reproducible instead of depending on stray host cache state.
Performance
Use the reproducible harness in benchmarks/ to compare KeyHog,
Betterleaks, Kingfisher, TruffleHog, and Titus under one scoring contract. The
harness excludes the ground-truth manifest from every scan tree. The generated
tables remain empty until current-schema runs exist. Run
make -C benchmarks report after measurement. Do not edit generated tables by
hand.
Detection leaderboard
No results for corpus mirror yet - run make leaderboard.
Speed & memory
No timed runs yet.
Per-category recall gaps (where a competitor still wins recall)
No keyhog result for this corpus yet.
Reproduce: make -C benchmarks bench runs every scanner on the 15k
SecretBench-mirror corpus and writes benchmarks/results/<host>/;
make -C benchmarks report regenerates the tables above and
benchmarks/reports/. See benchmarks/README.md
for the corpora (mirror, competitor home-turf, Samsung/CredData) and the
backend/cache/daemon/OS/GPU matrix.
Daemon mode
The optional Unix daemon keeps a compiled scanner warm for repeated eligible stdin and single-file scans. It runs in the foreground and is never started implicitly.
Omitting --daemon means auto on Unix. Bare --daemon means on, which
fails if the service cannot honor the request exactly. Directory, Git, remote,
verification, baseline, and policy-changing scans stay in process. See the
daemon workflow for eligibility, retry,
identity, socket trust, shutdown, coverage, and exit semantics.
Watch mode is a separate foreground filesystem-event loop; it does not connect
to the daemon socket or appear in keyhog daemon status. For IDEs:
System-wide credential triage
Enumerates every mounted drive (skipping pseudo-FS like /proc,
/sys, tmpfs, nsfs, fuse.snapfuse), auto-discovers every .git
(worktrees + bare repos + submodules), and runs the full scan +
git-history pipeline. Honors a hard --space <bytes> ceiling and
exits 1 on findings. Built for incident-response triage, M&A
inheritance audits, and quarterly developer-laptop sweeps.
Lockdown mode (security-critical embeddings)
For deployments where keyhog runs on the same machine that holds the secrets (e.g. paired with EnvSeal) and there is no trusted boundary between the scanner and the credentials it inspects:
Enforces:
mlockall(MCL_CURRENT|MCL_FUTURE)on Linux: credentials never page to swap.PR_SET_DUMPABLE = 0(always on, even outside lockdown): disables core dumps, ptrace,/proc/<pid>/memreads. macOS getsPT_DENY_ATTACH.setrlimit(RLIMIT_CORE, 0)on Linux: the kernel refuses to write any core file regardless of the systemcoredump_filter, so anonymous pages can never reach disk via the dump path.- Refuses to run if
~/.cache/keyhog/*exists, refuses--incrementalwrites, refuses--verify, refuses--show-secrets, refuses--fast/--no-decode/--no-entropy/--no-ml/--no-unicode-norm/--no-default-excludes(each trades off detection completeness for speed; lockdown is for the highest-stakes runs where you want every gate engaged).
The always-on hardening (everything except mlock + cache refusal) is
applied to every KeyHog invocation. Even without --lockdown, the
KeyHog process cannot be core-dumped or traced through ptrace.
Library API
use ;
use CompiledScanner;
// Built-in embedded detectors, parsed through the fail-closed loader.
let detectors = load_embedded_detectors_or_fail?;
let scanner = compile?;
let findings = scanner.scan;
The no-backend library methods are deterministic portable CPU references; they
do not consult host heuristics or the CLI's calibration cache. Use
scan_with_backend or scan_coalesced_with_backend for an explicit
Hyperscan/GPU engine. The keyhog CLI owns persisted fastest-correct autoroute.
The explicit-backend library methods return infallible finding vectors, so the
selected backend is a hard process contract: unavailable SIMD terminates with
exit 3, and unavailable or failed GPU execution terminates with exit 12
instead of returning findings from another engine. Call warm_backend to probe
startup eligibility; use the CLI as a subprocess when an embedder must contain
runtime accelerator failure.
Mix shipped + custom detectors by concatenating before compile. The
scanner is Send + Sync; share one across rayon workers. Streaming
source helpers in keyhog-sources (file-system, git, stdin, Docker,
S3, GCS, Azure Blob, GitHub org, GitLab group, Bitbucket workspace). Live verification in keyhog-verifier.
The library boundary is documented in the architecture guide and crate-level Rust docs.
Configuration
Per-repo defaults via .keyhog.toml:
[]
= "high"
= 0.40 # canonical default; raise toward 0.85 for fewer FPs
= ["**/test/fixtures/**", "vendor/"]
= "512MB" # optional; otherwise VRAM-adaptive
[]
= "10MB"
= "10MB"
= 100000
= "256MB"
= 1000
= "8GB"
[]
= false # accelerated slots use this same canonical id
[]
= 0.6 # per-detector floor; overrides the global one
[]
= true # refuse to run unless --lockdown is passed
[]
= "/home/alice/.cache/keyhog/autoroute.json" # or "off"
= "/home/alice/.cache/keyhog/calibration.json"
= false # true only for diagnostics/calibration
= "auto" # auto | off | required
[]
= [] # extra 12-digit canary issuer accounts
= [] # treated the same way: do not live-verify
[]
= true # scanner recall-route defaults; printed by config --effective
= 4096
= 320
= true
= true
= true
= false
= 30000
Precedence (rightmost wins): compiled defaults → .keyhog.toml
(walked up from the scan path) → CLI flags. The canonical defaults live in
ScanConfig::default() (crates/core/src/config.rs). Full reference:
docs/src/reference/configuration.md.
keyhog config --effective <path> prints the exact resolved scan and report
policy (without scanning), so the precedence chain is provable
(here a CLI --min-confidence 0.6 overrides the compiled 0.40 default):
Suppress a known finding by credential hash, path glob, or detector id in
.keyhogignore, with optional reason, expires, and approved_by
governance metadata. See Suppressions for rule
ordering, inline directives, and composable .keyhogignore.toml predicates.
# .keyhogignore - gitignore-style shorthand
*.log
node_modules/
9d6060e21ef8d5daec9cfe4a44b1b1bc9792246bfad28210edaaa1782a8a676a
# Explicit form with governance
hash:9f86d081… ; reason="rotated 2026-04-25" ; expires=2026-07-01 ; approved_by="security@acme"
detector:demo-token
path:**/fixtures/*.env
Entries past expires fail allowlist load with an actionable error, forcing the
approval to be renewed or removed before the scan can proceed.
Architecture
Contributor map: Architecture is the one-page guide to the whole repo: every top-level directory, the crate layering, and the bytes→finding pipeline with each stage pointing at the module that owns it. Start there to navigate the code.
crates/
core/ Detector loading, finding types, reporting (text/JSON/SARIF), allowlists
scanner/ Hardware routing, Hyperscan, GPU, decode-through, entropy, ML, multiline
sources/ File system, git (staged/diff/history), stdin, Docker, S3, GCS, Azure Blob, GitHub/GitLab/Bitbucket, web
verifier/ Live credential verification for detectors with an active `[detector.verify]` endpoint
cli/ CLI binary, daemon, watch, baselines, calibrate, hook installer
detectors/ 923 TOML files (data, not code)
docs/src/ Canonical mdBook documentation deployed to GitHub Pages
benchmarks/ Reproducible eval harness: corpus generators, scanner adapters, scorer, gate, README report generator
tools/ Contract generators (gen_contracts.py, gen_companion_contracts.py)
Two-phase coalesced scan:
- Phase 1: shared trigger scan on raw bytes, parallel across all files via rayon. The selected SIMD route uses Hyperscan; scalar and GPU routes keep their measured owners, and portable builds use the pure-Rust trigger path. Files with no trigger hit stop before extraction.
- Phase 2: full extraction on hits only: regex capture groups, companion matching, detector-owned offline validation, entropy gating, ML confidence + explicit Bayesian damping when configured. Its optional Hyperscan prefilter is likewise exclusive to the selected SIMD route.
Result: extraction work is concentrated on trigger-positive data. Determinism is part of the contract: same input → same output, byte-exact, every time.
The full pipeline, routing ownership, and profiling entrypoints live in the architecture guide and backend reference.
Other useful subcommands
Contributing
- New detector? Drop a TOML in
detectors/, open a PR. The contributor guide (CONTRIBUTING.md) has the schema and a worked example. - Bug / missed secret / false positive? File an issue with the
redacted credential shape and detector id; each report becomes a
permanent test fixture under
tests/contracts/. - Security issue in KeyHog itself? Don't open a public issue;
use GitHub private vulnerability reporting.
If that form is unavailable, email
security@santh.dev; PGP is not required.
Credits
KeyHog stands on prior secret-scanning work. Ideas borrowed from:
- TruffleHog: detector breadth and verification semantics
- Betterleaks: token-efficiency and false-positive suppression
- Titus: scanning ergonomics and severity calibration
Thanks to these projects and their contributors.
License
License: MIT OR Apache-2.0.
Terms: MIT and Apache-2.0. This dual license covers the code and detector TOMLs. Commercial use, embedding, forks, and hosted services are permitted under either license.
Star history
If keyhog has saved you from leaking a credential, a star is the cheapest way to tell the next person it exists.