Security: verify server host keys against known_hosts (TOFU) - #42
Merged
Merged
Conversation
Local: - KeyFileWriterService: truncate on overwrite (FileMode.Create), CreateNew otherwise, FileShare.None, and enforce 0600 on overwritten files since UnixCreateMode only applies on creation. - Create the backup directory (holds private key copies) and ~/.ssh with 0700. Remote: - Read/write authorized_keys and known_hosts via SFTP instead of shell command lines built from server-provided paths and file contents (broken quoting on Windows for entries containing '"', e.g. command="..."). - No more eager file creation on connect; missing files read as empty and are created with 0600 (dir 0700) on Unix hosts when written. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
SshClient was created without a HostKeyReceived handler, so SSH.NET accepted any host key. An attacker in the network path could impersonate the server, capture the password of PasswordConnectionCredentials and tamper with authorized_keys/known_hosts shown and written by the app. - KnownHostKeyStore checks the presented key against ~/.ssh/known_hosts (plain and hashed |1| names, [host]:port, wildcards, negation, @Revoked; @cert-authority is ignored). - ServerConnection rejects every key the validator does not trust, for both the SSH and the SFTP channel. - ServerConnectionService: unknown host -> dialog with SHA256 fingerprint, on confirmation the key is appended to known_hosts and the connection is retried; mismatch or revoked -> HostKeyVerificationException. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
Base automatically changed from
security/file-access-hardening
to
development
September 28, 2026 12:51
# Conflicts: # OpenSSH_GUI.Core/Lib/Misc/ServerConnection.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
ServerConnectioncreated itsSshClientwithout aHostKeyReceivedhandler, and SSH.NET then accepts every host key. That allowed a man-in-the-middle attack:PasswordConnectionCredentials, the attacker receives the password in plain text.authorized_keysandknown_hostscontent that the app displays and writes back.New
KnownHostKeyStore(Core) checks the presented key against~/.ssh/known_hosts. It supports plain and hashed (|1|salt|hash) host names,[host]:port, comma-separated lists, wildcards and negations (viaSshWildcardMatcher), and@revoked.@cert-authorityentries are ignored. The comparison uses the key blob; a mismatch is only reported for the same key type, matching OpenSSH.ServerConnectionrejects every key the validator does not trust, on both the SSH and the SFTP channel. Without a validator, it rejects everything.ServerConnectionServicehandles the three outcomes:SHA256:fingerprint. If the user confirms, the key is appended toknown_hostsand the connection is re-established.@revokedHostKeyVerificationExceptionwith an MITM warning, no dialog.HostKeyVerificationException.HostKeyTrustPrompt(UI) implements the dialog viaIMessageBoxProvider, marshalled to the UI thread, with strings in EN and DE.New
known_hostsentries are written as plain host names (lower case,[host]:portfor ports other than 22). A new file gets 0600.Base is
security/file-access-hardening(#40), because the validator is attached to the SFTP client introduced there as well.Changes Made
Testing Performed
Test Evidence
KnownHostKeyStoreTestswith 15 test cases. They cover plain, list, port, wildcard, hashed and negated entries, mismatch, a different key type,@revoked,@cert-authority,Add(including a missing trailing newline and 0600), and the fingerprint format. All 222 tests pass.sshd(port 2222, password auth) using the realServerConnectionService:HostKeyVerificationException(Unknown).known_hosts.known_hosts:HostKeyVerificationException(Mismatch), no prompt.authorized_keyswith the commentsfirst"quoted"$(id)andit's'$HOMEwas read and written back byte-for-byte. When.sshwas missing, the directory was created with 0700 and the file with 0600.Checklist
dotnet build,dotnet test)Questions for Reviewers
ConnectToServerViewModel.TestConnectionAsyncruns with a 5 s timeout. If the user needs longer than that in the TOFU dialog, the reconnect after confirmation fails with a cancellation. The key is already stored at that point, so the next attempt works. Should the dialog be excluded from the timeout?known_hostsis always taken from~/.ssh/known_hosts.UserKnownHostsFileorGlobalKnownHostsFilefromssh_configare not evaluated.Notes for Reviewers:
🤖 Generated with Claude Code
https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
Generated by Claude Code