Skip to content

Add SSH certificate, ProxyJump and manual host support - #42

Open
jappa wants to merge 1 commit into
mainfrom
ssh-certs-proxyjump-manual-hosts
Open

Add SSH certificate, ProxyJump and manual host support#42
jappa wants to merge 1 commit into
mainfrom
ssh-certs-proxyjump-manual-hosts

Conversation

@jappa

@jappa jappa commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

Remote SSH connections previously only worked for simple ~/.ssh/config entries: exact Host name match, no includes, no wildcards, no jump hosts, and a fallback to 127.0.0.1:22 whenever nothing matched. This PR makes remote connection handling work against realistic SSH setups.

Certificate authentication

ssh2 has no support for OpenSSH certificates. Two pieces are needed:

  • getKeyAlgos() in ssh2/lib/client.js must know how to pick a signature algorithm for ssh-rsa-cert-v01@openssh.com keys. That function is module-local and not exported, so it can't be monkey-patched at runtime — it's patched at install time by patches/ssh2+1.17.0.patch via a patch-package postinstall hook.
  • keyParser.parseKey and Protocol.prototype.authPK are patched in memory in src/main.ts, so the parsed key presents the certificate blob as its public key and the USERAUTH_REQUEST packet carries the cert algorithm in the pubkey field but the base algorithm inside the signature block, as OpenSSH servers require.

CertificateFile is now read from ~/.ssh/config and plumbed through to the connection.

SSH config parsing

  • Follow Include directives recursively, with glob expansion and cycle detection.
  • Match multi-host lines (Host a b c), wildcards, and negation (!foo).
  • First-value-wins precedence, matching OpenSSH.
  • Strip quotes from values.
  • Return null when no host matches, instead of a default 127.0.0.1:22 config that would connect to the wrong machine.

ProxyJump

establishSSHStream() resolves ProxyJump, handles chained jump hosts, expands %r/%n/%h/%p, and passes the forwarded stream as sock. Jump clients are tracked and cleaned up on both error and close.

Agent vs. explicit key

If an explicitly configured key is unencrypted, or IdentitiesOnly yes is set, the SSH agent is disabled. This fixes "too many authentication failures" where the agent burned all available attempts before the configured key was tried.

Manual host entry

New Saved Hosts / Manual Entry toggle with host, port, username and key-path fields. Successful manual connections are saved to customSshHosts in settings and merged into the dropdown. A manually typed name is still resolved against ~/.ssh/config, so aliases work. The dropdown gains a -- Select SSH Host -- placeholder rather than auto-selecting the first entry.

Verification

  • Reverted node_modules/ssh2/lib/client.js to pristine upstream 1.17.0 and ran npx patch-package; the result is byte-identical to the working patched file.
  • CI runs npm ci, which triggers postinstall, so the packaged asar ships already-patched. patch-package defaults to --error-on-fail under CI, so a future ssh2 bump that invalidates the patch fails the build loudly rather than silently disabling certificate auth.
  • npm run electron:build type-checks and compiles clean.
  • Lockfile changes are patch-package's own dependency tree plus npm hoisting previously-nested copies; the only version move is dev-only transitive semver 7.8.1 → 7.8.5.

Review notes

Not yet verified end-to-end in this branch. There is no automated coverage, and the certificate, ProxyJump and agent-disabling paths have not been run against a live server as part of preparing this PR. They need manual testing against a real cert-based host and a real jump host before merge. The authPK packet layout in particular is worth a close read.

🤖 Generated with Claude Code

Remote connections previously only worked for simple ~/.ssh/config
entries. This adds:

- OpenSSH certificate auth. ssh2 has no support for this, so
  getKeyAlgos() in ssh2/lib/client.js is patched at install time via
  patch-package (patches/ssh2+1.17.0.patch) and parseKey/authPK are
  monkey-patched in memory. CertificateFile is read from ssh config.
- ~/.ssh/config parsing: follow Include directives (with globs and
  cycle detection), match multi-host lines, wildcards and negation,
  and apply first-value-wins precedence like OpenSSH does. Return
  null on no match rather than a bogus 127.0.0.1 config.
- ProxyJump, including chained jump hosts and %r/%n/%h/%p tokens.
  Jump clients are torn down on error and close.
- Disable the SSH agent when an unencrypted key is configured or
  IdentitiesOnly is set, to avoid exhausting auth attempts.
- Manual host entry in the UI, persisted to customSshHosts in
  settings and merged into the saved hosts dropdown.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant