Commit Graph
193 Commits
Author SHA1 Message Date
Andy Dixon 2e11d40034 test: hostile-input tests for the remote-facing parsers
Seeded pseudo-random input, biased towards each parser's control bytes,
is fed to the IRC line parser and state machine (plus mIRC formatting),
Telnet IAC negotiation, VNC framebuffer updates (including ZRLE behind a
real zlib stream, with a check that the decoders are actually reached),
~/.ssh/config parsing and the update manifest. None panic, and Telnet's
pending buffer stays bounded.

These run on stable in every CI run; they are not coverage-guided, so a
cargo-fuzz setup (nightly) remains a possible follow-up.
2026-10-12 09:40:16 +01:00
Andy Dixon 0d0b3a7c75 fix: wipe long-lived credential copies from memory
Session-only credentials (kept for a whole session so reconnects don't
re-prompt), credentials pending until a connection succeeds, and the
credential prompt's text buffer are now Zeroizing and wiped when
dropped, including when a prompt is cancelled or abandoned
(SEC-20261012-007, partial).

Still open: the values handed to the protocol clients are plain Strings
for the duration of authentication, egui keeps its own copies of typed
text, and no mlock/PR_SET_DUMPABLE is applied.
2026-10-12 09:37:23 +01:00
Andy Dixon c6ee9a8f36 fix: macOS local shells no longer run lsof synchronously on every Enter
The working-directory capture (used to restart a local shell where it
left off) ran /usr/sbin/lsof inside write(), so each Enter in a macOS
local tab waited for a subprocess. On macOS the lookup now runs on a
background thread, triggered by the shell's next output after a line is
entered, so it sees the directory after the command; a failed lookup
keeps the last known directory. Linux keeps the cheap /proc readlink
(F10). Not exercised on macOS in this environment.
2026-10-12 09:31:15 +01:00
Andy Dixon 65558d82d1 fix: GitHub refresh reuses commit lookups and caps new ones per refresh
Every refresh looked up the latest commit of every repository (up to
5000), which can exhaust the API's hourly rate limit and then surfaces as
'authentication failed'. Lookups are now cached per repo against its
pushed_at, definite misses (404/409) included, and at most 100 new lookups
run per refresh, most recently pushed first (F13).
2026-10-12 09:24:40 +01:00
Andy Dixon 10f93271cf fix: probe the keychain with a read instead of writing an item at every launch
Startup wrote and deleted a probe item to see whether the keychain works,
waking D-Bus on Linux and risking a Keychain prompt on macOS. A read of a
missing entry answering 'no entry' proves the same; if a later write is
refused anyway, set_checked already falls back to the vault (F11).

Not verified against a live Secret Service or macOS Keychain in this
environment (no secret service here; the probe correctly reports it
unavailable).
2026-10-12 09:21:44 +01:00
Andy Dixon 954bf25dc0 fix: automation answers can come from NEXUS_ANSWERS instead of argv
--answer puts credentials in the process list and shell history.
NEXUS_ANSWERS (one answer per line) is read at startup and removed from
the environment so local shells don't inherit it, and either source now
prints a warning when used outside --screenshot (SEC-20261012-018).
2026-10-12 09:20:31 +01:00
Andy Dixon b325097602 ci: least-privilege token, SHA-pinned actions, cargo audit job
- ci.yml declares permissions: contents: read instead of the default
  token scope.
- Every action is pinned to the commit its current tag points at
  (checkout v4.4.0, upload-artifact v4.6.2, action-gh-release v2.6.2),
  so a moved tag can't change what runs with release write access.
- A cargo audit job runs on every push; the single ignored advisory
  (RUSTSEC-2023-0071, rsa via russh, no fix available) is recorded with
  its rationale in rust/.cargo/audit.toml (SEC-20261012-019, -012).

Release signing and macOS notarisation are not part of this change.
2026-10-12 09:19:57 +01:00
Andy Dixon bd1dc54e20 fix: validate Quick Connect and ~/.ssh/config imports; unknown enum values no longer reset a profile's config
- 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).
2026-10-12 09:19:03 +01:00
Andy Dixon 3b490fa6c3 fix: redact scripted login passwords from secret-free exports; secret kinds are an enum
- 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.
2026-10-12 09:16:14 +01:00
Andy Dixon 3843351365 fix: the vault checks the master password even when it holds no secrets
An empty vault accepted any password, so a typo at the first unlock
encrypted later secrets under the wrong key. New vaults store a verifier
(a known value sealed under the key) that every unlock must open. Vaults
from Go or older builds keep today's behaviour and gain a verifier on
their next write; Go ignores the extra field (SEC-20261012-016, F8).
2026-10-12 09:13:34 +01:00
Andy Dixon 4d6595588b fix: bound IRC state, read recording metadata from the file tail, cap the audit log
- IRC: channel state is kept only for rooms the client itself joined, so
  JOIN/PART/KICK/NAMES for other rooms can't grow it; users per channel
  are capped at 10 000 and the view keeps at most 200 conversations,
  dropping the oldest idle query (SEC-20261012-014, F14).
- Recordings: the list reads the header line and the last 64 KiB for the
  duration instead of whole files of up to 250 MiB, falling back to a
  full scan only when the final event is longer than the window
  (SEC-20261012-020, F9).
- Audit log: keeps the newest 10 000 entries (F17).
2026-10-12 09:12:25 +01:00
Andy Dixon f09300fd95 fix: a panic on one thread no longer takes the whole app down
Every shared lock was std::sync::Mutex with lock().unwrap() (~150 sites),
so a panic in any session's reader or callback poisoned the lock and the
next UI frame panicked too, dropping every open session. Locks are now
parking_lot::Mutex (no poisoning; already in the dependency tree), and a
panic hook reports worker panics as an error toast instead of the session
silently stopping (F3, F4).
2026-10-12 09:07:32 +01:00
Andy Dixon afec95bb36 fix: save typed credentials only after the connection succeeds, and re-ask when a saved one is rejected
A password typed at a 'saved' prompt was stored before the server
accepted it, so a typo was persisted and silently retried on every
reconnect. Typed values are now held per attempt and written once the
session connects; when the server refuses a saved credential the next
attempt asks again and the new value replaces it on success. A store
failure (keychain refused, vault locked) now shows a toast instead of
being swallowed, and reconnect prompts wait 5 minutes like the first
connect (SEC-20261012-015, F1, F2, F20).
2026-10-12 09:02:43 +01:00
Andy Dixon 741ee2ec5b fix: draw accented and other non-ASCII glyphs on the same baseline as ASCII text
Non-ASCII cells were centred vertically in the cell while ASCII runs were
top-anchored; the cell height is the row height rounded up, so é, ñ, €,
box-drawing and similar glyphs sat lower than their neighbours (more
visibly at larger sizes and on HiDPI). Combining marks on an ASCII base
(e + U+0301, as macOS filenames use) were also dropped by the ASCII run
path; such cells are now drawn individually with their marks.
2026-10-12 08:59:32 +01:00
Andy Dixon 534310b56d feat: preferences for remote clipboard writes, multi-line paste confirmation and the launch update check
OSC 52 and VNC ServerCutText now go through one handler that honours
'allow remote clipboard' and always toasts the first line, so a silent
clipboard swap can't set up a paste into another shell (SEC-20261012-010).
Multi-line pastes into a terminal without bracketed paste ask first, with a
preview (SEC-20261012-017). The launch-time update check can be turned off
and the updater docs no longer claim it is opt-in; the unused GitHub
fallback is removed (SEC-20261012-013, F15).
2026-10-12 08:57:30 +01:00
Andy Dixon 764208727c Fixed URL 2026-10-12 08:44:31 +01:00
Andy Dixon 082c0f8fa7 ui: more breathing room between a connection's name and user@host in the sidebar 2026-10-12 08:20:40 +01:00
Andy Dixon 33b17223aa feat: changed host keys can be rejected, accepted once, or accepted and updated
Under the 'ask' policy a changed host key used to be a hard error. The
prompt now shows the stored and received fingerprints and offers Reject,
Accept Once (connect, keep the record) and Accept & Update (replace the
record; audited). Unknown keys gain an Accept Once option too. Strict and
accept-new still refuse changed keys without prompting. Known-host rows
written by the old app are confirmed removable by id (test).
2026-10-12 08:19:41 +01:00
Andy Dixon 4b5ffe8e5e Merge branch 'rust-port' into master
Rust port of Nexus Terminal (nexus-core + egui app), security audit fixes
and reconnect credential handling.
2026-10-12 07:57:48 +01:00
Andy Dixon 17a5b4fbb5 fix: keep session-only credentials across reconnects so NickServ re-identifies without re-prompting
The NickServ password (and any other "remember for this session" secret) was
resolved through a fresh per-connect cache, so every auto-reconnect prompted
again with a 60 s timeout and an unanswered prompt failed the reconnect.
Session-only secrets now live on the session, survive auto and explicit
reconnects, and IDENTIFY is sent on every registration as before.
2026-10-12 07:53:03 +01:00
Andy Dixon 55a8295de7 docs: security audit and functionality/next-steps reports (2026-10-12 07:23) 2026-10-12 07:32:25 +01:00
Andy Dixon aac986d78b fix: bound VNC server-announced sizes and only open http(s) links from network data
- The VNC client refuses desktop sizes over 64 Mpx (ServerInit and
  DesktopSize/ExtendedDesktopSize) and ZRLE rectangles over 64 MiB before
  allocating, so a hostile server can't force a multi-gigabyte allocation.
- Release URLs from the update manifest and repository URLs from the GitHub
  API are handed to the system opener only when they are http(s) links.
2026-10-12 07:23:40 +01:00
Andy Dixon 0a18f7e578 fix: generate SSH keys in-process, verify loopback tunnel peer, bound SSH command output
- 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.
2026-10-12 07:22:31 +01:00
Andy Dixon 9cf221779b fix: purge keychain credentials on profile delete, atomic owner-only secret writes, zeroize derived keys
- Secrets::delete_profile removes every profile credential kind from the OS
  keychain as well as the vault (previously only the vault was purged, so
  keychain passwords/tokens outlived the profile).
- write_private writes to a sibling temp file and renames it into place, so a
  crash mid-write cannot truncate the vault or an export, and a pre-existing
  world-readable file is replaced rather than reused.
- Argon2-derived vault/export keys are Zeroizing and wiped on drop.
- Recordings (which capture typed input), JSON exports and --data-dir
  directories are created owner-only.
2026-10-12 07:20:11 +01:00
Andy Dixon 8766846a3b docs: illustrated LaTeX user guide (docs/guide) with generated screenshots 2026-10-11 19:41:31 +01:00
Andy Dixon ce9c5c6b44 Screenshot automation (--connect repeatable, --do, --answer, --shot-delay), SFTP Browser palette entry, centred dialogs, IRC opens the first joined room; demo_data example 2026-10-11 19:41:30 +01:00
Andy Dixon 84eff02c15 Move update and release URLs to dixon.cx/downloads/nexus 2026-10-11 19:06:48 +01:00
Andy Dixon 7b92b81ecd Serial/Telnet Send Break and DTR/RTS tab controls; fix Help and hints; drop unused log path; docs 2026-10-11 17:54:11 +01:00
Andy Dixon 8d798124e9 Merge branch 'docs-rewrite' into rust-port 2026-10-11 17:52:43 +01:00
Andy Dixon 8c646e7176 docs: rewrite for Nexus Terminal 2026-10-11 17:52:35 +01:00
Andy Dixon b3de13f0b7 docs: port status 2026-10-11 17:49:37 +01:00
Andy Dixon 1c16079584 Remove the Go backend, JS frontend, Wails config and old build scripts; release.sh now wraps rust/release.sh 2026-10-11 17:43:55 +01:00
Andy Dixon e1707e4751 rust: VNC remote clipboard to the local clipboard 2026-10-11 17:43:35 +01:00
Andy Dixon cc42a9086d rust: --connect startup flag; palette launcher hint fits narrow windows 2026-10-11 17:42:40 +01:00
Andy Dixon 2bb12c3a58 rust: merge UI feature modules; fix Ctrl+key on Linux/Windows (egui command_only shadowing); clippy clean; rustfmt config 2026-10-11 17:40:48 +01:00
Andy Dixon 327ca1caa9 Merge branch 'ui-views' into rust-port 2026-10-11 17:35:22 +01:00
Andy Dixon 3ed4454081 rust: RAW, IRC, GitHub and VNC views 2026-10-11 17:35:15 +01:00
Andy Dixon b646950738 rust: --open <window> for headless screenshot checks 2026-10-11 17:34:15 +01:00
Andy Dixon cce0bdb1fb Merge branch 'ui-sftp' into rust-port 2026-10-11 17:32:39 +01:00
Andy Dixon 19c5862288 rust: apply sidebar width from Settings; egui Panel::show 2026-10-11 17:32:39 +01:00
Andy Dixon 143e4900e0 rust: SFTP browser, file drop, port forwards 2026-10-11 17:32:07 +01:00
Andy Dixon 31d8d6fdae Merge branch 'ui-settings' into rust-port 2026-10-11 17:27:33 +01:00
Andy Dixon 580c7239ba Merge branch 'ui-tools' into rust-port 2026-10-11 17:26:02 +01:00
Andy Dixon 4ff59f6183 Merge branch 'ui-editor' into rust-port 2026-10-11 17:26:01 +01:00
Andy Dixon 4b17298925 rust: settings, SSH key manager, export/import 2026-10-11 17:25:56 +01:00
Andy Dixon 52770923df rust: snippets, triggers, recordings and playback 2026-10-11 17:23:54 +01:00
Andy Dixon 7f4f9376fb rust: profile editor and quick connect 2026-10-11 17:23:47 +01:00
Andy Dixon f80374db77 docs: build and release for the Rust app 2026-10-11 17:14:40 +01:00
Andy Dixon b1e18a8ffb rust: optimise the app crate in dev builds (terminal parser); throughput test 2026-10-11 17:14:11 +01:00
Andy Dixon 0f4a906914 rust: keychain calls off the tokio runtime; plan reflects as-built crate choices 2026-10-11 17:04:24 +01:00