Contributing to keyhog
June 19, 2026 ยท View on GitHub
Thanks for considering a contribution. The most useful contributions are the smallest: a false-positive report with a one-line snippet, a detector regex tweak, a missing test case. The repo is laid out so a new contributor can do real work after one read of this file.
Before opening a PR, please skim the Code of Conduct and the project's anti-rigging law: a test must assert truth, not shape; positive findings get a negative twin; every fix ships an adversarial test alongside the proving test.
Repo layout
crates/core/- shared types, detector specs, hardening, theSourceandReportertraits.crates/scanner/- the engine: compiler, AC + Hyperscan + GPU backends, suppression, confidence, decoding, entropy.crates/sources/- filesystem, git, web, archive backends behind theSourcetrait.crates/verifier/- live credential probes (auth/identity endpoints) for--verify.crates/cli/- thekeyhogbinary, subcommands, args, daemon.detectors/- 902 TOML detector specs, the project's moat. No Rust code touched when you add one.fuzz/- cargo-fuzz harnesses, one target per parser/scanner sink.ml/- Python model training and Rust/Python feature-parity checks for the scanner's embedded MoE weights.tests/,metrics/- repo-level integration tests, corpus gates, and perf dashboards..github/actions/keyhog/- the in-tree composite action consumers reference assanthsecurity/keyhog/.github/actions/keyhog@<tag>.
How to add a new detector
No Rust code required. Detectors are TOML manifests under
detectors/. Copy the closest existing detector and edit:
cp detectors/stripe-secret-key.toml detectors/my-service-key.toml
$EDITOR detectors/my-service-key.toml
The required keys live under [detector] and one or more
[[detector.patterns]] blocks. Fields:
id- unique kebab-case identifier, matches the filename.name- human-readable display name.service- lowercase vendor / protocol name.severity-info | low | medium | high | critical.keywords- case-aware substrings used to short-circuit the scan when none appear in a chunk. At least one is required.[[detector.patterns]]blocks - one or more, each with aregex, a one-linedescription, and agroupfor the match-group that captures the actual secret.[detector.verify](optional) - if the service has a public status / identity endpoint that returns 200 on a live key and 401/403 otherwise, fill this out and--verifywill probe it.
Run the in-tree validation gate before committing:
cargo test -p keyhog-core --test all_tests detector_
Then add a per-detector contract under
crates/scanner/tests/contracts/<detector-id>.toml with at least one
[[positive]], one [[negative]], and one [[evasion]] entry, and
run the contract runner:
cargo test -p keyhog-scanner --test contracts_runner
Finally, dogfood the scanner on the repo itself. Zero new findings are expected on a clean tree:
cargo run --release -- scan .
How to add a new source backend
Source backends live in crates/sources/src/. Each implements
keyhog_core::Source (crates/core/src/source.rs).
- Add a new module under
crates/sources/src/and gate it behind a feature flag incrates/sources/Cargo.tomlso consumers can opt in. - Implement
keyhog_core::Sourcefor your backend. Thefilesystemandgitmodules are the reference implementations. - Wire the backend into the CLI dispatch in
crates/cli/src/orchestrator/so a subcommand or flag selects it. - Add adversarial tests under
crates/sources/tests/covering the happy path and at least one failure mode (auth refusal, rate limit, malformed response).
How to add a new output format
Reporters live in crates/core/src/report/ and the dispatch lives
in crates/cli/src/reporting.rs. Each reporter implements the
keyhog_core::Reporter trait (crates/core/src/report.rs):
pub trait Reporter: Send {
fn report(&mut self, finding: &VerifiedFinding) -> Result<(), ReportError>;
fn finish(&mut self) -> Result<(), ReportError>;
}
- Add a new module under
crates/core/src/report/. Pattern-matchtext.rs,json.rs, orsarif.rs. - Re-export the type from
crates/core/src/report.rs. - Add the corresponding
OutputFormatvariant incrates/cli/src/args/enums.rs. - Wire it into
crates/cli/src/reporting.rs::write_findings. - Add a golden-file test confirming the byte-exact output for a canonical finding.
How to run tests
The workspace uses standard cargo commands. There is no
Makefile; the few one-off scripts live in scripts/. The most
commonly-used commands:
cargo test --workspace # full test suite
cargo test -p keyhog-scanner # only the engine
cargo test -p keyhog-core --test all_tests # core invariants
cargo clippy --workspace --all-targets # advisory lint visibility (not a hard gate)
cargo build --release -p keyhog # production binary
cargo run --release -- scan . # dogfood
cargo bench -p keyhog-scanner # microbenchmarks
For the multi-hour adversarial / corpus suites, see
tests/README.md or the workflows under .github/workflows/.
Code style
We follow standard Rust conventions enforced by cargo fmt and
cargo clippy:
- Import ordering:
std, external crates, internalcrate::. - Types
PascalCase, fns + varssnake_case, constsSCREAMING_SNAKE_CASE. - Booleans read as questions (
is_valid,has_findings). - Zero
unwrap()/expect()in non-test code; propagate with?. - Error messages lowercase, actionable, specific. Include the fix the user should try whenever possible.
- Public items have doc comments.
- Files cap at ~500 lines. Split when you cross it.
PR checklist
Before opening a PR:
-
cargo test --workspacepasses. -
cargo clippy --workspace --all-targets -- -D warningsis green. - If you added a detector, you also added a positive fixture AND an adversarial (false-positive) fixture.
- If you fixed a bug, you added a test that fails before your patch and passes after.
- No stubs (
todo!(),unimplemented!(), no-op loops,// TODOplaceholders). - New public items are documented.
- Commit messages explain why, not just what.
Reporting a vulnerability
Do not open a public issue for a security report. Use GitHub's private advisory flow at /security/advisories/new or email security@santh.dev.
Contributors
keyhog is built by the Santh team and sharpened by community contributions. Every merged PR earns a line here - thank you to everyone who has sent a fix, a detector, or a test:
- @Eraminel01 (Edyard) - corrected the Anthropic API key detector shape (#7).
New contributors: add yourself to this list in the same PR as your
change. One line, newest last: [@handle](profile) - what you improved ([#PR](link)).