Skip to content

fix(ssh_config): honor UserKnownHostsFile and GlobalKnownHostsFile #278

Description

@inureyes

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

  • A config naming a UserKnownHostsFile that contains the host's key connects successfully under StrictHostKeyChecking yes.
  • The same config pointing at an empty file fails, and the two outcomes are distinguishable.
  • GlobalKnownHostsFile /dev/null and UserKnownHostsFile /dev/null parse and behave as empty stores.
  • Multiple paths per keyword and the %-token expansions are covered by tests.
  • accept-new writes the learned key into the resolved user file, not into ~/.ssh/known_hosts.
  • The regress tests connect, connect-uri, connect-bigconf, brokenkeys, login-timeout, penalty and reconfigure pass in the harness.

Part of #275

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions