Files
nexus/audits/20261012-0723-functionalityNextSteps.md
T

16 KiB
Raw Permalink Blame History

Functionality and Next Steps Report

Project: Nexus Terminal — Rust port (rust/ workspace) Branch / base commit: rust-port @ 8766846 (after this run: aac986d) Date: 2026-10-12 07:23 (local) Companion: audits/20261012-0723-securityAudit.md (SEC-20261012-NNN references below)

1. Executive Summary

The port is functionally complete relative to the Go/Wails app and is in good shape: the core crate is cleanly separated from the UI, every protocol client follows one lifecycle contract (blocking connect, own reader thread, on_close exactly once), the test suite (142 tests) exercises real sockets, a real sshd, PTYs and fake IRC/VNC/GitHub servers, and clippy -D warnings is clean.

The highest-impact improvements now are reliability and user-experience rather than new protocols:

  1. Credential UX — a mistyped “saved” password is persisted before it is verified and then blocks every reconnect with no re-prompt (SEC-015); store failures are silent.
  2. Resilience — a panic on any worker thread poisons shared mutexes and takes the whole app down (lock().unwrap() everywhere); there is no panic hook, no crash message and no recovery of open sessions.
  3. Product/privacy switches — update checks, remote clipboard writes and multi-line paste all happen without a preference or notice.
  4. Operability — recording metadata, audit-log growth and the IRC state model scale poorly with long-running use.
  5. Developer experience — fuzz targets, a CI audit step and a documented cargo clean note after moving the checkout would have saved time in this run.

2. Current Functional Risks

# Risk Where Effect on users
F1 Saved credential stored before auth succeeds; no path to replace it on failure other than editing the profile core/src/sessions.rs secret_fn Typo → every connect fails silently with the wrong saved password
F2 Secrets::set swallows errors (let _ =) core/src/secrets.rs “Save securely” silently doesn't save when the keychain refuses and the vault is locked
F3 Mutex poisoning: ~60 lock().unwrap() sites; any panic in a callback thread (e.g. a decoder bug) crashes the UI on the next lock sessions.rs, app.rs, clients One bad session kills all sessions, recordings stop mid-write
F4 No panic hook / crash dialog; eframe aborts silently app/src/main.rs Users see the window vanish with no explanation
F5 Quick Connect (SSH) allows an empty username; connection then fails at auth with an opaque server error app/src/quick_connect.rs, service.rs connect_quick (unvalidated) Confusing failure
F6 import_ssh_config writes profiles without validate_profile (e.g. empty User) core/src/service.rs Profiles that fail validation when later edited
F7 Unknown enum value in a stored JSON column (hostKeyPolicy, lineEnding, …) makes parse_col return None; the profile silently reverts that protocol's config to defaults core/src/storage.rs parse_col Lost settings after a downgrade/edit with a newer app
F8 Empty vault accepts any master password; a typo on first use creates entries under the wrong key (SEC-016) core/src/secrets.rs Unrecoverable secrets
F9 Recording listing reads every .cast fully (up to 250 MiB each) on the UI's worker thread (SEC-020) core/src/recording.rs read_meta Recordings window stalls with large files
F10 macOS capture_cwd runs /usr/sbin/lsof on every Enter in a local shell core/src/clients/local.rs Noticeable latency on macOS local tabs
F11 Keychain probe writes and deletes an item at every launch core/src/secrets.rs Keychain::available Keychain prompts/noise on macOS; D-Bus wake-ups on Linux
F12 Keychain legacy read-through copies AlchementaryLink items forward on every miss Keychain::get Repeated writes for keys that only exist in the legacy service
F13 GitHub refresh performs up to 5000 commit lookups every refresh interval with 5 threads core/src/clients/github.rs Token rate-limit exhaustion; 403s surface as “auth failed”
F14 IRC: channels/users tracked for any JOIN/NAMES seen, UI targets created for any sender (SEC-014) irc.rs, irc_view.rs Memory growth on busy networks
F15 check_github in updater.rs is unused (dead code) and hardcodes an owner/repo that may not exist core/src/updater.rs Misleading; GitHub fallback never runs
F16 Update check at every launch, no preference, docs say opt-in (SEC-013) app/src/app.rs Privacy expectation mismatch
F17 Audit log has no cap/rotation storage.rs add_audit DB grows indefinitely
F18 Stale target/ after moving the checkout bakes old CARGO_MANIFEST_DIR into fixture tests (4 failures until cargo clean) build hygiene Confusing CI-vs-local mismatch
F19 Secrets::delete_profile relies on a static list of kinds (PROFILE_SECRET_KINDS); the profile editor and session manager still use string literals for kinds secrets.rs, profile_editor.rs, sessions.rs A new kind added in one place is missed by the purge
F20 Reconnect credential prompt timeout is 60 s vs 300 s on first connect sessions.rs schedule_reconnect Reconnects fail if the user is away briefly

3. Code Quality Improvements

  • Secret kinds as a type: replace the &'static str kinds ("password", "github-token", "vnc-ssh-passphrase", …) scattered across sessions.rs, profile_editor.rs and secrets.rs with an enum (SecretKind) that has as_str() and ALL. Why: F19 and SEC-001 depend on the list staying complete. Safe step: enum + From<&str>; keep the string form for storage keys.
  • Poison-tolerant locking: introduce a small fn lock<T>(m: &Mutex<T>) -> MutexGuard<T> that recovers from poisoning (unwrap_or_else(|e| e.into_inner()), as irc.rs/vnc.rs already do) and use it in sessions.rs, ssh.rs, app.rs. Why: F3. Mechanical refactor; existing tests cover behaviour.
  • Panic hook: install std::panic::set_hook in main.rs that logs the payload (no locals) and posts a toast via post_error; keep worker panics from killing the UI. Why: F4.
  • parse_col should not swallow errors: log the parse error and keep the raw JSON (or surface a toast once) instead of returning None. Why: F7. Add #[serde(other)] variants to the enums so unknown values map to a named Unknown instead of failing the whole struct.
  • sessions.rs (1 300 lines): split per-protocol connect functions (connect_ssh/irc/vnc/github) and the reconnect state machine into modules; the file is the hardest to test in isolation. Behaviour-preserving move; tests/sessions.rs guards it.
  • Thread-per-event: on_close spawns a thread per close and each connect spawns one; fine for a desktop app but makes ordering hard to reason about. Consider one std::sync::mpsc worker for lifecycle events.
  • Duplicated defaults: Profile::new, Profile::normalize, profile_editor::prep and SshConfig::defaults each know the default port/keepalive/retry values. Centralise in models.rs constants (already partly done) and have prep call normalize.
  • run_command("pwd") is the only use of exec for state; SFTP realpath(".") already gives the home — consider dropping the shell call to avoid shells that print banners.
  • Keychain::available probe: cache the result in preferences with a timestamp; re-probe weekly or on explicit request. Why: F11.
  • Dead code: updater::check_github, Protocol::Local branches in validation (unsupported protocol). Remove or wire up.
  • Store::migrate: wrap each migration in a transaction and record schema_version explicitly in code rather than relying on the SQL files to insert it (verify each file does).

4. Frontend Interaction and Error-Handling Improvements

The UI is native egui; the equivalent of “event firing / async failures / console diagnostics” was reviewed:

  • Async failures surface: every worker reports through Job, Inbox, post_error or Shared::spawn; the pattern is consistent. Gap: Secrets::set (F2) and let _ = self.store.update_profile(&p) in connect_profile discard errors — toast them.
  • Modal keyboard handling: prompts() and dialogs() read Key::Enter/Escape globally; with two modals stacked (a credential prompt while the Delete dialog is open) one key can act on both. Route keys only to the top-most modal (check app.prompts.is_empty() before dialogs read Enter).
  • Credential prompt: show which credential kind and profile; add “Save this credential” checkbox when the profile is in ask-every-time mode; after an auth failure with a saved secret, offer “Re-enter”.
  • Host-key prompt: offer “Copy fingerprint” and show the port; on key change, show both fingerprints side by side with a “Remove old key” action (today the user must go to Settings → Known Hosts).
  • Clipboard from remote (SEC-010): toast with the first line whenever OSC 52 or VNC sets the clipboard; preference to disable.
  • Multi-line paste (SEC-017): confirmation preview when the terminal is not in bracketed-paste mode.
  • Transfers: SFTP upload stops at the first failure and drops the rest of the queue (by design) — show which files were skipped.
  • Console diagnostics: adopt tracing with RUST_LOG support for connection lifecycle (host, protocol, state, error) — never credentials, never terminal data; today only eprintln! for CLI misuse exists.
  • Crash fallback (F4): panic hook + “Nexus Terminal hit an internal error; sessions were kept open where possible” dialog.
  • Screenshot automation flags (--answer): read from NEXUS_ANSWERS env/file instead of argv (SEC-018) and print a warning when used without --screenshot.

5. Testing Roadmap

  • Unit: secret_fn ordering (store after success), Vault verifier (once added), parse_col unknown-enum behaviour, IRC state caps, read_meta tail parsing, export_json redaction of login-script sends.
  • Integration: extend tests/ssh_integration.rs with password + keyboard-interactive auth (sshd with PasswordAuthentication yes and a PAM-less test user is awkward; alternatively a russh server fixture), host-key change detection against the known-hosts table, remote forward teardown, SFTP rename/mkdir/remove.
  • Abuse-case / fuzz: cargo-fuzz targets for vnc::read_update (+ decoders), telnet::Inner::process, irc::parse_line + ircformat::format_event, recording::Store::get, sshconfig::parse, updater::parse_manifest. Run in CI nightly with a corpus from the existing fake-server tests.
  • UI interaction: the --screenshot --do --answer automation already drives the app headlessly; turn it into a smoke test matrix in CI (connect to the in-test sshd, open SFTP, run a palette command, assert the PNG is non-empty and the process exits 0).
  • Regression: keep the Go-fixture compatibility tests; add a fixture written by this version so future format changes are caught both ways.
  • Load: the ignored perf::feed_throughput test (50 MiB through the VT parser) should run in release mode in CI with a threshold; add a VNC decode benchmark on a synthetic 4K frame.
  • Smoke: cargo audit and cargo deny check licenses advisories jobs; cargo test --locked already runs on three OSes.
  • Preferences: check-for-updates on/off (SEC-013), allow remote clipboard writes (SEC-010), confirm multi-line paste (SEC-017), confirm before insecure host-key policy connects.
  • Credential management: a Settings → Credentials page listing stored kinds per profile with “forget” buttons; master-password change (re-encrypt vault); export/import of the vault.
  • Known hosts: import from ~/.ssh/known_hosts and export to it; SSH certificate CA trust (@cert-authority) since russh already parses certificates.
  • Update integrity: signed manifest (minisign/Ed25519), verified before display; optional in-app download with sha256 + signature check.
  • IRC: SASL PLAIN/EXTERNAL (modern networks reject NickServ IDENTIFY over plaintext), IRCv3 message tags for timestamps, per-network ISUPPORT line length.
  • Serial: session logging to file, hex view like RAW, break duration setting.
  • SFTP: recursive directory upload/download, drag-out to the desktop, resume on reconnect.
  • Session: “Send to all tabs in group”, per-session environment overrides for local shells, custom shell arguments.
  • Operability: audit-log viewer with cap/rotation; recordings metadata cache; “Diagnostics” page (keychain status, data dir, versions of russh/rustls, last update check).

7. Next-Level Opportunities

  • Observability: tracing + optional file log with redaction; connection timing metrics in the status bar; opt-in crash reports (local file only).
  • Resilience architecture: supervisor-style session threads that catch panics per session (std::panic::catch_unwind around client callbacks) so one protocol bug cannot end the app.
  • Security architecture: move all secret-bearing fields out of Profile into the secret store behind a migration (keeps .nexus compatibility by exporting them inline only when “include secrets” is chosen); Zeroizing/SecretString throughout; Linux PR_SET_DUMPABLE while the vault is unlocked.
  • Distribution: signed, notarised builds; Flatpak/AppImage with sandboxing (--filesystem=home:ro + explicit ~/.ssh), winget/Homebrew casks; SLSA provenance from the release workflow.
  • Performance: VNC — upload only dirty rows to the texture (TextureHandle::set_partial), decode JPEG on the I/O thread (already) but avoid the full local.clone() on resize; terminal — batch paint runs per style (already) and cache galleys per row.
  • Accessibility: egui accesskit is enabled; audit focus order in modals and provide screen-reader labels for icon-only buttons (widgets::icon_button has hover text only).
  • Developer experience: justfile/make targets for test, clippy, audit, screenshot-smoke; a CONTRIBUTING.md note about cargo clean after moving the checkout (F18); fuzz corpus checked in.

8. Prioritised Action Plan

Immediate (this week) — low risk, high value

  • Add the three preferences (update check, remote clipboard, paste confirmation) with safe defaults. Risk: trivial.
  • Toast on Secrets::set failure; store saved credentials after auth success (F1/F2). Risk: touches the auth path; covered by sshd tests — add a keyboard-interactive case.
  • Poison-tolerant lock helper + panic hook (F3/F4). Risk: mechanical.
  • Add cargo audit to CI with SEC-012 ignored and dated (outside rust/, owner's call).

Short term (next release)

  • IRC state caps and joined-only tracking (F14).
  • parse_col error visibility and Unknown enum variants (F7).
  • Vault verifier entry (F8); keychain probe caching (F11).
  • Known-hosts import/export; host-key change dialog improvements.
  • Recording metadata from file tail (F9).

Medium term

  • Secret-kind enum and credential management page (F19).
  • Fuzz targets + nightly job; screenshot smoke matrix.
  • Signed update manifest; release signing.
  • SASL for IRC; SFTP recursive transfers.

Long term

  • Move profile-embedded secrets into the store with migration; SecretString everywhere.
  • Per-session panic isolation; tracing-based diagnostics.
  • Sandboxed distribution and provenance.

9. Caveats and Validation Needed

  • Keychain behaviour (purge on delete, probe cost, legacy copy-forward) was verified by reading keyring usage, not by running against macOS Keychain, Windows Credential Manager or a live Secret Service; test on each OS before release.
  • The in-process key generator (ssh::generate_key_pair) was tested for Ed25519 and ECDSA; RSA generation runs the rsa crate's keygen (slow in debug builds — verify the UI stays responsive and that OpenSSH accepts the files on all three platforms).
  • --data-dir now creates directories 0700; if any user relies on sharing a portable data directory between accounts this changes behaviour.
  • The VNC size cap (64 Mpx) is above any real display; confirm with multi-monitor spanning setups (e.g. 3×4K = 37 Mpx) before lowering it.
  • Preference defaults for update checks and remote clipboard are product decisions; both have compatibility implications (vim/tmux yank over SSH relies on OSC 52).
  • UI event flows were validated by code review and the existing automation flags, not by an interactive run in this environment.