Security: change key passphrases in-process instead of via ssh-keygen - #41
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
ssh-keygen was invoked with -P "<old>" -N "<new>" on the command line: both passphrases were readable by other local users (/proc/<pid>/cmdline, process listings) and quotes/spaces in passphrase or path broke argument parsing, allowing additional ssh-keygen arguments to be injected. The decrypted key is already loaded, so it is now re-encrypted with SshNet.Keygen and written back in its original format (OpenSSH, PuTTYv2, PuTTYv3). This also removes the OpenSSH round-trip for PuTTY keys and the runtime dependency on ssh-keygen being on PATH. 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
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
SshKeyManager.ChangePasswordOfKeyAsyncstartedssh-keygen -p -f {path} -P "{old}" -N "{new}"./proc/<pid>/cmdlineand in process listings (ps, WMI), so other local users and processes could read them."in a passphrase or a space in the key path broke the argument parsing. That produced a wrong passphrase or passed additionalssh-keygenarguments.The decrypted key is already loaded in memory. It is now re-encrypted with SshNet.Keygen (
ToOpenSshFormat/ToPuttyFormatwithSshKeyEncryptionAes256, orSshKeyEncryptionNonefor an empty passphrase) and written back in its original format throughKeyFileWriterService. The detour through OpenSSH for PuTTY keys and the runtime dependency onssh-keygenare both gone. Only the private key file is rewritten; the.pubfile does not change when the passphrase changes.Depends on #40: rewriting relies on
FileMode.Createtruncating the file. Without that, bytes of the old key stay at the end when the passphrase is removed. The base is thereforesecurity/file-access-hardening; once #40 is merged, GitHub retargets this PR todevelopment.Changes Made
Testing Performed
Test Evidence
New file
SshKeyManagerPasswordTestswith 3 tests:",-Nand spaces;Each test checks the result on disk by loading the file with
PrivateKeyFile.All 210 tests pass. There is no
ssh-keygenin the test environment, so the old implementation fails all three tests. That run does not show the quoting issue in isolation.Checklist
dotnet build,dotnet test)Notes for Reviewers:
🤖 Generated with Claude Code
https://claude.ai/code/session_01XSUJwZ17AWMdDvYXboJAbQ
Generated by Claude Code