- known_hosts: the @cert-authority pattern matcher was recursive and
backtracking; a pattern of a few hundred thousand stars overflowed the
connect thread's stack on every handshake and '*a*a*a…b' went
exponential. It is now the iterative two-pointer match, and patterns
over 1 KiB are refused on import and in Trust a CA.
- Agent-forward and remote-forward channels are only honoured on the
final hop: a jump host could previously open auth-agent channels and
reach remote-forward destinations on the client machine.
- Remote forwards with no bind address listen on the server's loopback
(as ssh -R does) instead of every interface.
- Keyboard-interactive gives up after 10 rounds; dials through jump hosts
and run_command have timeouts; the handshake timeout leaves room for
the host-key prompt so a slow reader isn't timed out mid-decision.
- Known hosts are looked up per key type, so an imported known_hosts with
ed25519 and RSA rows no longer produces a false 'key changed' warning.
- SFTP download suggests a sanitised base name in the save dialog.
- sync: timestamps and tombstones in the shared file are clipped to the
local clock, and an edit wins a tie with a deletion, so a forged
future-dated record can no longer override every later local change
(or keep deleting a re-created profile) for as long as the forged date.
- write_private: the temp file is created O_EXCL under a random name, so a
symlink pre-placed in a shared folder can't be followed or overwritten.
- Vault/keychain reads return Zeroizing<String>; has_sync_password is
cached for a few seconds instead of fetching the real value every frame.
- generate_ssh_key creates a missing ~/.ssh as 0700.
- Honest wording: encrypted exports and the sync file carry login-script
values; the audit entry, the Settings hint and the docs now say so.
- Settings → Security lists every profile's stored credentials (kind and
whether it's in the keychain or the vault; the keychain is probed per
kind on a background thread since it can't be listed) with Forget.
- Change Master Password re-encrypts every vault entry under a fresh
salt and key with a new verifier, after checking the current password,
in one atomic write (Vault::change_password; tested that the old
password stops working and every value survives).
- Settings → Diagnostics shows version, platform, data/database/log
paths and sizes, keychain and vault state, counts, sync and launch
settings, with Copy Diagnostics; it never includes credentials.
Openable with --open settings:diagnostics.
Profiles can be kept in step across machines through any folder they
share (cloud drive, network share, git checkout): one file,
nexus-profiles.nexus, in the encrypted-export format under a sync
password kept in the keychain/vault. Each sync merges profile by profile
(newest updated_at wins), propagates deletions as dated tombstones (an
edit after a deletion brings the profile back; tombstones expire after
180 days), validates every incoming profile, writes the file only when
it changed, and never syncs credentials.
Settings → Security → Profile sync picks the folder and sets the
password; syncs run at launch, 3 s after a profile change, every 10
minutes and on Sync Now (automatic runs stay quiet unless something
changed). An end-to-end test drives create, edit and delete between two
data directories sharing a folder.
- Host certificates: certificate authorities are trusted per OpenSSH host
pattern (stored as @cert-authority known-hosts rows, matched against
'host' or '[host]:port' as OpenSSH does, with *, ? and !). For a host a
CA covers, the client asks for a certificate; one signed by a matching
CA, of host type, naming the host and in date is trusted without a
first-use prompt, and anything else is refused. Hosts no CA covers keep
the existing key checks. Applies to jump hosts and tunnels too.
- User certificates: <key>-cert.pub next to the configured private key is
used for publickey login (falling back to the plain key).
- Settings → Known Hosts: import ~/.ssh/known_hosts (hashed names and
@revoked lines skipped and counted), export in OpenSSH format, and a
Trust a CA form.
Verified against OpenSSH sshd with ssh-keygen-issued certificates: host
cert accepted without prompting and user-cert login with only
TrustedUserCAKeys; a CA for other hosts falls back to the key check; a
different CA is refused.
The tab layout (tab order and names, pins, the active tab, split trees
with their ratios) is saved whenever it changes — checked once a second,
written only on change, so it survives a crash — and reopened at the
next launch: saved profiles reconnect and local shells start in the
directory they were in (on Linux the live /proc cwd, so it isn't one
command behind). Quick Connect tabs and placeholder panes are skipped.
A General setting turns it off; it never runs when launched with
--connect, --run-local or --open, and screenshot runs never save.
Verified: a local tab left in /usr/share came back there with a
'Restored 1 tab(s)' toast.
The Go-generated link.db, profiles.link and rec1.cast fixtures and the
tests that read them are removed. Their coverage is kept by tests on
data this build writes: a fresh database round-trips SSH profiles (env
vars, login scripts, forwards), serial profiles, IRC room state, known
hosts, snippets and preferences, and encrypted exports round-trip and
reject a wrong password. The import dialog no longer offers .link files
and the docs stop mentioning them.
Keychain reads no longer fall back to the AlchementaryLink service and
copy entries forward, and Windows Credential Manager entries use
keyring's own target naming instead of go-keyring's 'service:user'.
Credentials saved by Alchementary Link, or by earlier builds on Windows,
are no longer found and need re-entering.
SshConfig no longer has proxyPassword or totpSecret fields, so neither
can sit in clear in nexus.db or travel in an export. The proxy password
is a SecretKind (profile:<id>:proxy-password) edited like other saved
credentials and purged with the profile; the session manager hands both
it and the TOTP seed to the SSH client from the keychain/vault at
connect time.
This drops compatibility with Go-era profiles and .link/.nexus files
that carry these values inline: they are ignored on read and must be
re-entered (SEC-20261012-011).
- Quick Connect runs the same validation as saved profiles, so an SSH
connection without a username fails up front with a clear message (F5).
- ~/.ssh/config imports fill a missing User (and jump-host user) with the
local user name, as ssh does, and skip blocks that still don't validate,
reporting how many (F6).
- An unrecognised hostKeyPolicy/lineEnding/parity/flowControl/direction in
a stored config falls back to that field's default instead of failing
the whole JSON column and silently resetting every other setting; parse
failures that remain are logged (F7).
- A JSON export without secrets now blanks login-script 'send' steps that
answer a password-like 'wait' prompt (SSH and Telnet), not just the
proxy password and inline TOTP seed (SEC-20261012-011, partial).
- Credential kinds were string literals in the session manager, profile
editor and secret store; they are now SecretKind with one ALL list, so
the profile-delete purge can't miss a kind (F19).
Moving proxyPassword out of the profile record is left open: it changes
.link/.nexus compatibility with the Go app and needs an owner decision.
- SSH key pairs are generated with ssh-key (Ed25519/ECDSA P-256/RSA) and
encrypted in-process, so the passphrase no longer appears as an ssh-keygen
command-line argument visible in the process list.
- open_tunnel's loopback bridge checks the accepted peer is our own client
socket, so a local process racing for the ephemeral port can't be spliced
into the IRC/VNC SSH tunnel.
- run_command caps buffered output at 1 MiB; local-forward abort handles are
pruned as connections finish; SOCKS5 host/credential lengths over 255 bytes
are rejected instead of being truncated into a malformed request.