sshconfig-lint
Lint your ~/.ssh/config for common mistakes.
Checks for duplicate host blocks, missing identity files, wildcard ordering problems, weak algorithms, duplicate directives, and more. Supports Include directives with cycle detection.
https://github.com/user-attachments/assets/4d995679-baed-4f20-9ba8-8f3ec94c64fd
Install
Quick install (Linux / macOS)
|
Set VERSION=v0.1.0 or INSTALL_DIR=~/.local/bin to override defaults.
macOS (Homebrew)
optional untap Noah4ever/tap to remove the tap and keep the tap list clean
Cargo
AUR
sshconfig-lint-bin - pre-built binaries
Pre-built binaries
Grab a binary from the releases page.
Usage
# lint the default ~/.ssh/config
# lint a specific file
# json output
# treat warnings as errors (useful in CI)
# skip Include resolution
Example output
line 4: [warning] WILDCARD_ORDER (wildcard-host-order) Host 'github.com' appears after 'Host *' (line 1); it will never match because Host * already matched (hint: move Host * to the end of the file)
line 7: [warning] DUP_HOST (duplicate-host) duplicate Host block 'github.com' (first seen at line 4) (hint: remove one of the duplicate Host blocks)
line 3: [error] MISSING_IDENTITY (identity-file-exists) IdentityFile not found: ~/.ssh/id_missing (hint: check the path or remove the directive)
Output is sorted by file and line number so it's deterministic across runs (stable for CI diffs and snapshots).
Errors are red, warnings are yellow, info is cyan. Colors are auto-disabled when stdout isn't a terminal or when NO_COLOR is set.
Exit codes
| Code | Meaning |
|---|---|
| 0 | Clean, no errors found |
| 1 | At least one error-level finding (or warning with --strict) |
| 2 | Config file not found |
Rules
Each finding has a stable code you can grep for or match on in scripts.
| Code | Rule | Severity | Description |
|---|---|---|---|
DUP_HOST |
duplicate-host |
warning | Two Host blocks with the same pattern |
MISSING_IDENTITY |
identity-file-exists |
error | IdentityFile path doesn't exist |
WILDCARD_ORDER |
wildcard-host-order |
warning | Host * appears before specific patterns |
WEAK_ALGO |
deprecated-weak-algorithms |
warning | Weak or deprecated algorithm (3des-cbc, arcfour, hmac-md5, ssh-dss, etc.) |
DUP_DIRECTIVE |
duplicate-directives |
warning | Same directive repeated in one scope (only first value takes effect) |
INSECURE_OPT |
insecure-option |
warning | Dangerous setting like StrictHostKeyChecking no or ForwardAgent yes on Host * |
UNSAFE_CTRL_PATH |
unsafe-control-path |
warning | ControlPath missing %h, %p, %r (or %C) — connections may share a socket |
INCLUDE_CYCLE |
include-cycle |
error | Circular Include chain |
INCLUDE_READ |
include-read |
error | Included file can't be read |
INCLUDE_GLOB |
include-glob |
error | Invalid Include glob pattern |
INCLUDE_NO_MATCH |
include-no-match |
info | Include pattern matched no files |
Findings include a hint when possible, like "move Host * to the end of the file".
What it handles
- Multiple host patterns (
Host github.com gitlab.com) - Multiple include patterns (
Include conf.d/*.conf extra.conf) - Inline comments (
IdentityFile ~/.ssh/id # my key) - Quoted values (
ProxyCommand "ssh -W %h:%p bastion") - Include resolution with cycle detection
Development
Project layout
src/
main.rs CLI
lib.rs Public API (lint_file, lint_str)
model.rs AST types
lexer.rs Tokenizer
parser.rs Builds config AST from tokens
resolve.rs Include expansion + cycle detection
report.rs Text and JSON formatters
rules/
mod.rs Rule trait and runner
basic.rs Built-in rules
tests/
fixtures/ Sample config files
cli.rs CLI integration tests
integration.rs Fixture-based tests
Adding a rule
- Implement the
Ruletrait insrc/rules/basic.rs - Register it in
run_all()insrc/rules/mod.rs - Write tests first, then make them pass
See CONTRIBUTING.md for more detail.
License
MIT