Part of the OpenSSH drop-in compatibility epic.
Problem
bssh parses UserKnownHostsFile and GlobalKnownHostsFile, validates the paths, merges them in the resolver, and then ignores them. Host key verification always reads ~/.ssh/known_hosts.
src/ssh/known_hosts.rs:20:
pub fn get_default_known_hosts_path() -> Option<PathBuf> {
dirs::home_dir().map(|home| home.join(".ssh").join("known_hosts"))
}
Both StrictHostKeyChecking::Yes and StrictHostKeyChecking::AcceptNew call it unconditionally. user_known_hosts_file and global_known_hosts_file appear only at src/ssh/ssh_config/resolver.rs:120-124, where they are assigned, and nowhere else in the tree.
This is security-relevant, not cosmetic. A configuration that pins a host's key to a specific file is silently pinned to a different file. An operator who deliberately isolates a host's trust store gets the user's ambient trust store instead, and nothing reports the substitution.
Reproduced against a live sshd on 2026-08-26: a config naming a UserKnownHostsFile that contains the correct [127.0.0.1]:4242 entry is rejected with "The host is not in known_hosts and strict host key checking is enabled", and pointing the same keyword at an empty file produces the identical error. The two cases are indistinguishable because neither file is read.
Second defect in the same area
bssh rejects GlobalKnownHostsFile /dev/null outright:
Security violation: Known hosts file path '/dev/null' at line 7 points to sensitive system location. Access to system files is not allowed for security reasons.
The failure is fatal and aborts config parsing, so bssh cannot load a configuration that OpenSSH accepts. Pointing a known-hosts keyword at /dev/null is the standard OpenSSH idiom for disabling that store, used by the regression suite and by a large amount of automation. A validator that is stricter than OpenSSH on a path the user explicitly named is not adding safety; it is refusing a valid configuration.
Scope
- Resolve the known-hosts paths from the effective config for the target host, honoring both keywords, with
UserKnownHostsFile taking precedence in the order OpenSSH defines and both accepting multiple whitespace-separated paths.
- Support the tilde and
%d, %h, %p, %r, %u token expansions OpenSSH applies to these keywords.
- Fall back to
~/.ssh/known_hosts only when neither keyword is set.
- Accept
/dev/null and none for both keywords, meaning "this store is empty", and restrict the security validator to what it can actually justify: refusing to write a learned key into a path the user did not name.
- Keep the existing first-use recording and cross-process serialization behavior, but target the resolved path rather than the default one.
Acceptance criteria
Part of #275
Part of the OpenSSH drop-in compatibility epic.
Problem
bssh parses
UserKnownHostsFileandGlobalKnownHostsFile, validates the paths, merges them in the resolver, and then ignores them. Host key verification always reads~/.ssh/known_hosts.src/ssh/known_hosts.rs:20:Both
StrictHostKeyChecking::YesandStrictHostKeyChecking::AcceptNewcall it unconditionally.user_known_hosts_fileandglobal_known_hosts_fileappear only atsrc/ssh/ssh_config/resolver.rs:120-124, where they are assigned, and nowhere else in the tree.This is security-relevant, not cosmetic. A configuration that pins a host's key to a specific file is silently pinned to a different file. An operator who deliberately isolates a host's trust store gets the user's ambient trust store instead, and nothing reports the substitution.
Reproduced against a live sshd on 2026-08-26: a config naming a
UserKnownHostsFilethat contains the correct[127.0.0.1]:4242entry is rejected with "The host is not in known_hosts and strict host key checking is enabled", and pointing the same keyword at an empty file produces the identical error. The two cases are indistinguishable because neither file is read.Second defect in the same area
bssh rejects
GlobalKnownHostsFile /dev/nulloutright:The failure is fatal and aborts config parsing, so bssh cannot load a configuration that OpenSSH accepts. Pointing a known-hosts keyword at
/dev/nullis the standard OpenSSH idiom for disabling that store, used by the regression suite and by a large amount of automation. A validator that is stricter than OpenSSH on a path the user explicitly named is not adding safety; it is refusing a valid configuration.Scope
UserKnownHostsFiletaking precedence in the order OpenSSH defines and both accepting multiple whitespace-separated paths.%d,%h,%p,%r,%utoken expansions OpenSSH applies to these keywords.~/.ssh/known_hostsonly when neither keyword is set./dev/nullandnonefor both keywords, meaning "this store is empty", and restrict the security validator to what it can actually justify: refusing to write a learned key into a path the user did not name.Acceptance criteria
UserKnownHostsFilethat contains the host's key connects successfully underStrictHostKeyChecking yes.GlobalKnownHostsFile /dev/nullandUserKnownHostsFile /dev/nullparse and behave as empty stores.%-token expansions are covered by tests.accept-newwrites the learned key into the resolved user file, not into~/.ssh/known_hosts.connect,connect-uri,connect-bigconf,brokenkeys,login-timeout,penaltyandreconfigurepass in the harness.Part of #275