Releases

v0.55.1 · stable
### Fixed

- **Android build.** The v0.55.0 auto-unlock strength warning added a
  `SidecarStrength` helper to the desktop machine layer but not to the
  Android stub (auto-unlock is disabled there), breaking the Android
  compile. Added the parity stub (always reports "none"). Desktop
  unaffected.

---

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 fbb6be62a1680157dc1b8fc7d05b18071e36e56445658f0bfcf75408662858ad
linux-amd64 · ssh-tool-linux-amd64 28.6 MB · sha256 010d3afcd9817a249954662f95eee1a44fdc2e6dfb504b7fc153b99ae8f180ce
linux-arm64 · ssh-tool-linux-arm64 27.0 MB · sha256 ac8f81ff67ef1b3cdd10812c15c8c5a6347cc3a7e92f3320248f90d6f22f6b5c
windows-amd64 · ssh-tool-windows-amd64.exe 29.6 MB · sha256 c6f51aa9333a7e2189c5ca7e22967c8e97d5c094b6bc1d3c09afc3d81a09c58f
windows-arm64 · ssh-tool-windows-arm64.exe 27.6 MB · sha256 aad03e0c6e34b349cdda4aa86db341fb089bcfa6ff66b548c501f08aa2756229
v0.54.0 · stable
### Added

- **Send a tab to another window.** Right-click a tab and pick
  **Send to <window>** to move it to any other open window - useful for
  pushing a terminal onto a second monitor without opening a new window.
  (A native drag can't cross window boundaries in a WebView, so this is a
  menu action.) The session stays live; works between the main window and
  detached windows in any direction.
- **Move a single pane to its own tab.** A split pane's toolbar now has a
  button to pop just that one pane out into its own tab, leaving the rest of
  the split intact (unlike "Ungroup tabs", which splits every pane out).

### Fixed

- **Detaching a tab no longer spills query-response garbage into the remote
  shell.** Rebuilding the terminal on detach replayed the scrollback, which
  could make xterm re-answer terminal queries (colour / device-attributes) in
  the replayed history and send those answers to the shell as junk like
  `2RR0;276;0c10;rgb:...`. Those responses are now suppressed during replay.
- **Redocking a detached window brings back all its tabs.** Previously only
  the first tab returned and the rest were lost with their sessions left live
  in the background (visible in the counter and as green in the tree, but
  unreachable).
- **The connection detail header stays visible while the form scrolls** - the
  name, Save, Connect, Use-different-credential and Delete actions no longer
  scroll out of reach on a long form.
- **Connect failures also raise a toast**, so a failed connection is visible
  even when you've scrolled the form down past the top-of-form error banner.

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 84cfbf480da1ce6ea7cb7ac727e63478ddba0ab9876d841cb05ff70ddc244a80
linux-amd64 · ssh-tool-linux-amd64 28.6 MB · sha256 494df799c6bfb5de6bf8d6dc6e52b9602989cbb28f3abd2c1537290cf94bf2c5
linux-arm64 · ssh-tool-linux-arm64 27.0 MB · sha256 171a6a7a5477f1cb727d4eb5bd5eca3a7dd6835b67c4e2923fa0069dd66b9752
windows-amd64 · ssh-tool-windows-amd64.exe 29.6 MB · sha256 c2201d0005c811362cc5b4f9f7353e07f98a3dadc7eca0de0b498a9870c7740c
windows-arm64 · ssh-tool-windows-arm64.exe 27.6 MB · sha256 21118950ae742ed913f60cce5d9489269e7365232ceb54aa325c71858257a5a3
v0.53.0 · stable
### Added

- **LLM activity log.** Everything a shared LLM does - run, type, connect,
  read - is now recorded and visible in an **LLM activity** panel: the
  command, whether it auto-ran or needed your approval, the exit status, and
  (for runs) the captured output, expandable per row. Open it from the robot
  icon in the status bar (all sessions) or from a session's Share-with-LLM
  popover (that session). It reads newest-at-bottom like a terminal and
  auto-scrolls. A toggle under LLM settings also keeps a durable copy in the
  audit log so it survives restarts.
- **Desktop notifications + taskbar flash for prompts that need you.** When
  the app is in the background and something blocks on you - an LLM approval
  request or a host-key confirmation - ssh-tool now flashes the taskbar and
  pops an OS notification with the actual ask ("An LLM wants to run a command
  on <host>"), so a prompt you're waiting on from your LLM client in another
  window doesn't sit unseen. The notification is opt-out (on by default) under
  LLM settings; the flash clears when you focus the window or answer.
- **System-prompt doc for LLM clients.** `docs/MCP_SYSTEM_PROMPT.md` is a
  ready-to-paste system prompt that teaches an LLM client how to use the
  ssh-tool tools well and safely (start with list_sessions, treat terminal
  output as untrusted, respect approvals). Drop it in your CLAUDE.md (Claude
  Code) or the system prompt (LM Studio).

### Fixed

- **A session the LLM opened via connect now shows a terminal tab.** Previously
  a headless connect from the MCP bridge left the session live but without a
  visible tab; it now appears and switches into view.
- The status-bar robot icon only shows when a session is actually shared with
  an LLM (it was showing whenever the bridge was enabled).
- The LM Studio `mcp.json` snippet now escapes the Windows binary path so it's
  valid JSON.

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 d6760a898998eda9fb577bb1cf6d2436603050f3fb4ab72891a2dd6dff34dc44
linux-amd64 · ssh-tool-linux-amd64 28.6 MB · sha256 2e607e23114e14ccc63dec99b5bf850c13ae98c59f80d45efa0fe2683ca9d778
linux-arm64 · ssh-tool-linux-arm64 27.0 MB · sha256 b51a3fc7fd52b6cec33af96d3a514af19546f234da634a289617f2cd51cacbe5
windows-amd64 · ssh-tool-windows-amd64.exe 29.5 MB · sha256 3534d89977bdf7525235211a30487dbc545b0561294e72d8b8ac82d37d8d34ef
windows-arm64 · ssh-tool-windows-arm64.exe 27.6 MB · sha256 3b52bdf672ec8b4d0d80463a55c2e21d22b3417b137c4523f9ee09e184fd3ef4
v0.52.0 · stable
### Added

- **"Give internet" - one-click reverse proxy for a server with no
  outbound net.** Open the tunnels popover on a connected session and
  click **Give internet**: ssh-tool raises a reverse tunnel on the
  server (loopback `127.0.0.1:3182` by default, overridable) and serves
  the proxying itself - no squid, no external tooling. It shows a ready
  to paste `export http_proxy=...` block; run that on the server and its
  HTTP/HTTPS traffic (apt, curl, wget, pip, dnf) flows out through your
  machine. DNS is resolved on the ssh-tool side, so the server does not
  need a working resolver for proxied traffic. The running proxy appears
  in the popover with live byte counters and a Stop button, and tears
  down automatically when the session disconnects.

- **Share a live session with an LLM (MCP bridge).** You can now attach
  an external LLM client (Claude Code, etc.) to a running SSH session so
  it can help you debug - read what's on screen, pull logs, propose and
  run commands. It's off by default: enable it under
  **Settings -> LLM (MCP) access**, register ssh-tool once with your LLM
  client, then share a specific session with the **Share with LLM**
  button in the pane toolbar (read-only or read+run). The registration command
  is shown for Claude Code and as an `mcp.json` block for LM Studio (any
  MCP client works the same way). The bridge is local-only (a unix
  socket on Linux/macOS, a loopback pipe on Windows) - nothing is
  exposed to the network, and no session is reachable until you share
  it. Read-only commands run on their own; anything that could change
  state pops an approval prompt where you Run it, type it into your
  terminal without pressing Enter, or Deny. The LLM can also **search
  your saved connections and open one** (by name/folder only - hostnames
  aren't exposed until a connect, which you approve, and the new session
  is then shared automatically). A shared session shows a badge on its
  tab so you always know what the LLM can see. If your LLM client runs
  in WSL while ssh-tool runs on Windows, enable the optional
  token-guarded loopback-TCP listener. Terminal output handed to the LLM
  is treated as untrusted data, never as instructions. Grants are
  cleared when the session disconnects. Desktop only.

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 19e96a28934decabd6216985633b218c9ea295f266e3af1349138a181ceaf50b
linux-amd64 · ssh-tool-linux-amd64 28.5 MB · sha256 bd19b09c7f2778c1ba9a3f006071e54ba6bcd69c8113c7eccbc9517eb1013139
linux-arm64 · ssh-tool-linux-arm64 26.9 MB · sha256 d2d9637220e630ca5a1e8a77035c17cd48d2b987a965ae02f1aea1bb3ee6f882
windows-amd64 · ssh-tool-windows-amd64.exe 29.0 MB · sha256 df1490c20535bfbaa8e33b3cc366c5c03d7205317fad55a83503457c7d728230
windows-arm64 · ssh-tool-windows-arm64.exe 27.1 MB · sha256 e2293b8c3c23d1509b768994b0f0a78f3b02163974bc3ef39643507d9bd4b809
v0.51.0 · stable
### Added

- **Credential expiry dates.** API tokens, setup keys and auth keys are
  usually time-limited - you can now set an **Expires** date on a
  credential (API token, password, or SSH key) when you create or edit
  it. The credential list shows an amber "expires in Nd" badge when one
  is within two weeks of lapsing and a red "expired" badge once it has,
  so a dead token is visible at a glance instead of surfacing as a
  mystery auth failure. Leave the date blank for no expiry.

### Fixed

- **Native dropdowns rendered white on the dark theme (Linux).** The
  `<select>` popup lists (credential kind, conflict mode, ...) drew
  white-on-white under WebKitGTK because the engine wasn't told the UI
  is dark. They now follow the theme. Windows was unaffected.

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 7748135921149570fb5fe46c473c5ef9dbe4bfad203b39ce889aec5eef958f42
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 c00cd5eced015c3e21cad40152e7bc8eae4995d48c419d16f3ec4b94395dba87
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 858bb7269637032a5bd91acb56506a8230ede0712cff03f5008934120b2cb8bf
windows-amd64 · ssh-tool-windows-amd64.exe 27.6 MB · sha256 5312553feb702f3187a30c0f68fe6a961bb995301c590e829cf244d1fb96b2a0
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 7539082c9558b286cb0b859eee250d3f136c0fb8c602c656ef43803a8ab36282
v0.50.0 · stable
### Added

- **Tailscale network profiles.** Tailscale joins WireGuard and NetBird
  as a userspace tunnel kind: route a connection's first SSH hop through
  your tailnet with no TUN adapter and no admin. Like NetBird it runs as
  an optional one-click plugin and each machine registers as its own
  node, so a synced profile is safe across machines. A profile takes a
  control URL (blank for Tailscale's own, set for self-hosted
  Headscale), a hostname (pre-filled from the machine name), and a
  reusable `tskey-auth-` key stored as a credential. Desktop only (the
  helper is a separate process Android can't spawn). See the guide's
  Network-profiles section.

### Changed

- **VPN helpers now update on their own schedule.** The NetBird (and new
  Tailscale) sidecar helpers ship on a separate release track, decoupled
  from the app version, so a helper can be patched without an app update
  and updating the app no longer forces a helper re-download unless the
  underlying protocol actually changed. The helper and app negotiate a
  protocol version on start; a mismatch now reports a clear "update the
  helper" / "update ssh-tool" message instead of failing obscurely.
  First upgrade from 0.49.0 re-downloads the helper once (it predates
  the versioned protocol), then stays decoupled.

android-arm64 · ssh-tool-android-arm64.apk 22.4 MB · sha256 1a0c3869ad4d8efe89ef6d67e8c9321d539b08bd210643b947fe06c05ff86028
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 a29ded56b3699d1c3d8a1b73ac5bef4daac614dcfebc67c0f31df8b2e992ada1
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 125f952926bc10f76b5154de57c7d8a1e5c073b7a278469c159b6d9dd3a6d547
windows-amd64 · ssh-tool-windows-amd64.exe 27.6 MB · sha256 e3e50b3232b7ec50423c02d9f29789527d0ee6eb532fde2762d256f7d0b2a4a8
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 172a0902a217afe5452905ea77bf68b380fb6dd7209916783093969a8203e39f
v0.49.0 · stable
### Added

- **Take over a WireGuard / NetBird profile that's live on another
  machine.** When the same network profile is shared over sync, only
  one machine can hold its userspace tunnel up at a time. Try to
  connect through a profile another machine already has up and the app
  now offers to take it over: it writes a kill request through sync,
  shows a countdown while the other machine tears its tunnel down, then
  connects - no more racing the disconnect button not knowing what's
  happening on the other end. "Connect anyway" is still there if you
  really want both up. Presence is published through the existing sync
  channel (a small plaintext presence file, no secrets), so it only
  works when sync is configured; single-machine users see nothing new.
  The take-over offer now covers every connect path - saved
  connections, bulk connect, dynamic-folder hosts, and a manual
  dynamic-folder refresh - not just single saved connections.

- **NetBird device name defaults to "<hostname>.ssh-tool".** The
  create form pre-fills the device name from this machine's hostname so
  a peer is recognisable in the NetBird dashboard and distinct per
  machine, instead of every peer registering as a bare "ssh-tool". Still
  editable before saving; a profile left blank falls back to the same
  derived name at connect time.

- **Dynamic-folder hosts and guests sort by name.** Proxmox (and other
  provider) entries render alphabetically now, case-insensitive and
  numeric-aware (web-2 before web-10). Guests are one flat alphabetical
  list across the whole cluster - in a multi-node Proxmox setup you find
  a VM by its name, not by which node it runs on.

- **See and free a busy profile from Settings.** Settings -> Network
  profiles now shows an "up on <machine>" badge for a WireGuard profile
  whose tunnel is live on another synced machine, with a "Disconnect on
  <machine>" button that asks that machine to drop it - a plain hand
  free-up without bringing the tunnel up locally (distinct from the
  take-over the connect flow offers).

### Fixed

- **"Tunnel running on another machine" never showed.** Presence
  identified each machine by a UUID kept in the store - which the store
  syncs, so both machines ended up with the SAME id and each read the
  other's presence record as its own, hiding the conflict entirely.
  Presence now identifies a machine by its stable hardware/OS id (the
  same value the auto-unlock sidecar is bound to), which never travels
  through sync, so two machines sharing a profile always differ.

- **Network profiles didn't sync.** Two independent gaps, both fixed:
  creating / editing / deleting a WireGuard / NetBird profile didn't
  mark the profile as changed, so auto-sync never pushed it; and even
  when a snapshot did carry a profile, the live pull (the no-restart
  apply) mirrored every table EXCEPT network_profiles, so the pulling
  machine got the connections that inherit a VPN profile but never the
  profile itself. The visible symptoms were profiles that never arrived
  on the other machine and inventory folders logging "network profile
  <id> not found" on every refresh (a connection's inherited profile
  pointing at a row that never came across). Profiles are now part of
  the sync change signal AND the live-pull mirror. The upgrade
  re-baselines silently: a machine that already has profiles pushes
  them on next launch; a machine without any doesn't push an empty
  update over one that has them.

android-arm64 · ssh-tool-android-arm64.apk 22.3 MB · sha256 c419598e502bb35b3ecb33490b3cc78dce23ec32666e7278fd6c59e3cb534a77
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 75b26f6ea37380de632011cd7eb5a191840960db5ca3783f2317591b4171776d
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 2e0a12f1caccd19858fa4345a03fbab05e0928d33563ff6de86acb783d20d5c1
windows-amd64 · ssh-tool-windows-amd64.exe 27.5 MB · sha256 e2f178ad8fca8b5caa9dcf2bf87793147f548971003474300ce99a253bd79a01
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 a96ee4a05cd30320bbb6b52f4b071ec9f799174c134ec4dcf7612ec5d3a88392
v0.48.2 · stable
### Fixed

- **Opening Settings -> Network profiles briefly flashed a console
  window on Windows.** The version check that shows the installed
  NetBird helper's version spawned it without hiding the console;
  it's now suppressed, like the tunnel process already was.

android-arm64 · ssh-tool-android-arm64.apk 22.3 MB · sha256 a1ffeb9a5a607ce86ccd1751453d6693896266f788e65f3429ee0aa53aa2317f
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 30a350b194565f226bed7f970bb415a6c3b1d4305feb38bcb5ebf14759a276b9
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 780b30637b68869eb2b24551e51398fc059ef4bdaf521df1243b7ed92476e390
windows-amd64 · ssh-tool-windows-amd64.exe 27.5 MB · sha256 12fd9f9ad394710c3f87284dce7227e66c6015d978d13682b88519739ae95ad4
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 f6497892fcf10145a6876d0a1a14c350284a757ff5b1e78201f68aee80d4a402
v0.48.1 · stable
### Fixed

- **v0.48.0 was stamped "v0.48.0-rc2" internally.** The release build
  ran while the rc2 tag still sat on the same commit as the release
  tag, and the version derivation picked the wrong one. Cosmetically
  that showed the wrong version in Settings -> About; more importantly
  it broke the NetBird plugin download, which looks for the helper in
  the release matching the app's own version - and rc2 had been
  deleted. Builds now stamp the exact tag that triggered them. Update
  from v0.48.0 to get a correctly-stamped app and a working plugin
  download.

android-arm64 · ssh-tool-android-arm64.apk 22.3 MB · sha256 255aaaadb6f87462499c729b95394a41aa8254798912e5b3fd9ce335d150b86d
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 42c93187d9bcac3f610675e192f3dadb115aa3e4f6578653abb8062adba70179
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 4501dad995465a5c646f2f31097daa603b65b138afa9764a24aa66df8dee7221
windows-amd64 · ssh-tool-windows-amd64.exe 27.5 MB · sha256 afdab31d92fd4cb6b5e3a01fb412aac66eb0756439981f4a12ebe4b369d0b821
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 8a33bd8d0994f6676b3cd796a51e1fdc74fe7259aa647b23d688a51ec566df2b
v0.48.0 · stable
### Added

- **Userspace VPN profiles.** Route a connection's first SSH hop
  through an overlay VPN so you can reach hosts on a private network -
  a client's WireGuard, a Hetzner internal network, a NetBird tailnet -
  without a system-wide VPN. Everything runs in userspace: no TUN
  adapter, no admin rights, no system routes, and several tunnels can
  be up at once. Manage profiles in Settings -> Network profiles;
  assign one to a folder or connection via its Network setting, which
  inherits down the tree so a whole client folder can go through one
  tunnel. A pane whose hop went through a tunnel shows a VPN badge, and
  the status bar lists running tunnels.
- **WireGuard**, built in. Paste a standard wg-quick config; the
  private key and preshared keys live in the vault. DNS servers in the
  config resolve hostnames inside the tunnel. Editing shows the config
  back with secrets as a **KEEP** placeholder so you can tweak without
  re-pasting keys.
- **NetBird**, via an optional plugin. Install it in one click from the
  Plugins card (downloaded from the matching release and checksum-
  verified). A profile takes a management URL, a device name, and a
  setup key (stored as a credential; a + New button creates it inline).
  Unlike WireGuard's single shared identity, each machine registers as
  its own NetBird peer - the right choice for connecting from several
  machines. See docs/netbird-setup.md for which key to use and how
  groups / access policies work.
- **Connect policy per profile.** Always (first hop always tunnels),
  Auto (probe direct first, tunnel only when that fails - direct
  on-site, tunnel remote), or Pause (a kill switch that dials direct
  and stops the tunnel). Tunnels start on demand and stop about two
  minutes after the last session using them closes. Dynamic-inventory
  folders can fetch their provider API through a profile too, for a
  Proxmox reachable only over the VPN.

  WireGuard works everywhere including Android; NetBird is desktop-only
  (its helper is a separate process Android can't spawn). A synced
  WireGuard profile carries one identity across machines - a warning in
  the editor explains the trade-off and points at NetBird for
  multi-machine use.

### Fixed

- **Linux: the app now has an icon and identifies itself properly.**
  The window / taskbar / tray showed no icon and the taskbar hover said
  "wails app"; the tray icon rendered as "...". The app now ships its
  icon, sets its program name and window class to match the launcher
  entry, and the tray uses a PNG the GTK tray can actually draw.
- **Linux: "Restart to apply" after an update now relaunches the app.**
  It used to close and never come back (a manual launch worked) because
  the new instance inherited the closing app's systemd scope and was
  killed with it. The relaunched instance now detaches into its own
  session.

android-arm64 · ssh-tool-android-arm64.apk 22.3 MB · sha256 af50371bc51d80869a2c2de3bca8279befacbae40aeb99d53467463bd10d1a6a
linux-amd64 · ssh-tool-linux-amd64 27.0 MB · sha256 e8e24c4018cc40f3d82b40b0f2f34aef7e253128d92329b1b632dfb02a30b13f
linux-arm64 · ssh-tool-linux-arm64 25.5 MB · sha256 7cbb884bb95a74b15b920ae36222e502ec1852cdda065c8f2588dcefe497b97e
windows-amd64 · ssh-tool-windows-amd64.exe 27.5 MB · sha256 8544d6e4c563164ee4f6e31407337faaeb29cb2a5d6e6526353269629c830986
windows-arm64 · ssh-tool-windows-arm64.exe 25.7 MB · sha256 fd763b00a4da2e341b95826961517f720683b7730dbd14b32821b51227575da5
v0.47.0 · stable
### Added

- **Jump from a terminal straight to its connection's settings.** New
  gear button in the pane title bar (next to the tunnels button):
  switches to the Connections view with that connection selected,
  ancestor folders expanded and the row scrolled into view. Handy
  when you want to add a port forward or tweak a setting without
  hunting the connection down in a large tree.
- **Profile statistics in Settings -> About.** Connection count (and
  how many have VNC enabled, inheritance included), folders (and how
  many are dynamic), dynamic inventory broken down into hosts / VMs /
  LXC containers / cloud servers, configured tunnels with their
  bookmark count, credentials, open sessions and live tunnels.
- **Expand / collapse all folders.** One toggle button in the
  Connections and Credentials sidebar headers: collapses everything
  when any folder is open, expands the whole tree otherwise.

### Changed

- **The WebGL terminal renderer is now off by default.** On some
  GPUs the WebGL glyph atlas corrupts into garbled glyphs - at times
  spontaneously, with the app sitting idle - so every terminal now
  uses the reliable canvas renderer out of the box. If you had
  explicitly toggled the WebGL setting before, your choice is kept.
  Opting back in (Settings -> Terminal) is worthwhile mainly for
  very heavy output; theme changes now also clear the glyph atlas
  for opted-in users, and Ctrl+Shift+L still forces a clean redraw.

### Fixed

- **Closing a tab or pane leaves the keyboard in the next terminal.**
  After Ctrl+D with auto-close enabled, closing a tab with the X /
  Ctrl+Shift+W, or closing one pane of a split, the promoted
  terminal now receives focus immediately - no extra click before
  you can type.

android-arm64 · ssh-tool-android-arm64.apk 19.9 MB · sha256 f3e1d495a6f5786c07b56353370dd6d8ef2d9e658a05e228bea2fa5e86fa9811
linux-amd64 · ssh-tool-linux-amd64 22.4 MB · sha256 09da501b29ed552a4176344dfc98c7ba89e244df18ac19c4040b0874a4f17fbe
linux-arm64 · ssh-tool-linux-arm64 21.1 MB · sha256 3a37533e8edb9bedeb0ad8805ed951b36be2413498b54d26368c159d0bee3527
windows-amd64 · ssh-tool-windows-amd64.exe 22.9 MB · sha256 43c3eccb3b8247c5249ccb956f1fec03278af34e74bf9a3942fd1b7b7d1e902c
windows-arm64 · ssh-tool-windows-arm64.exe 21.3 MB · sha256 c32fdfbe18cd3d8eed52887d11f489546817e03dfdd75567d7aa5da9b164e7f2
v0.46.0 · stable
### Added

- **"Open in ssh-tool" in the file manager's right-click menu.**
  Right-click a directory (or the background inside one) and pick
  Open in ssh-tool: the app opens (or focuses, if already running)
  with your default local shell as a tab, already in that directory -
  like "Open in Terminal", but inside the window that holds your SSH
  sessions. Install/remove it from Settings -> Connection -> File
  manager integration; per-user, no admin rights. Supported in
  Windows Explorer (on Windows 11 it appears under "Show more
  options" - the modern top-level menu needs app signing, tracked in
  the backlog), KDE Dolphin and the GNOME Nautilus Scripts menu.
  Also works from scripts: `ssh-tool --open-dir <path>`. On Windows
  the WSL shell lands in the /mnt/... equivalent automatically.

### Fixed

- **Light terminal themes: the character under the block cursor was
  invisible while the cursor blinked.** The glyph inside the cursor
  is now painted in the theme's background color (a proper inverse),
  in the live terminal and the recording player alike. Dark themes
  were never affected.

android-arm64 · ssh-tool-android-arm64.apk 19.9 MB · sha256 9bae6f8baa789e77c08d9fd4e51bfea6d4cd0b19f813a3009407f9b88cc5aa5f
linux-amd64 · ssh-tool-linux-amd64 22.4 MB · sha256 1e77de8134d6d20e1870bc92a2e66bab4a169c4cf1f5ad7294444b8e8a71b564
linux-arm64 · ssh-tool-linux-arm64 21.1 MB · sha256 2aef32c5c05a8c15059613daba2fbc0617dc1eb5e3b49f79663663c86e7bf757
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 ffc67ccc0c9fba58cc93bdd7457f6a3853de604812f2f4cef3efb23f921724d4
windows-arm64 · ssh-tool-windows-arm64.exe 21.3 MB · sha256 ee7b0c2a67e61c9dce26754e71ab111b348c67f1bc3c4383d31d811d0b776cfb
v0.45.2 · stable
### Changed

- **Every release, including the Android APK, is now built and
  published by CI.** The APK is compiled and signed in the release
  pipeline with the same key as previous builds (a pipeline gate
  verifies the signature, so a wrongly-signed APK can never ship)
  and attached to the GitHub Release next to the desktop binaries.
- **sshtool.app is now a mirror.** The website follows GitHub
  Releases and this repo's feature manifest on its own; downloads
  for new versions redirect to the GitHub release assets. Nothing
  changes for users - update checks, download URLs and the releases
  page keep working, including for installs older than v0.45.0.
- The Settings -> Updates text now describes the actual check:
  GitHub Releases first, sshtool.app as fallback.

android-arm64 · ssh-tool-android-arm64.apk 19.9 MB · sha256 e39627260fd6d957e986c57a8ec67747cc85c71cd99b8ad5c4a714918515845c
linux-amd64 · ssh-tool-linux-amd64 22.4 MB · sha256 b2d6079cec2345f65f00a01c094086521f721034179d28c67ff6546951ea3cba
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 847e7bcaba1e29fc1cac97200bb049c4074d0afb9f094c26b9a122c14ff2d767
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 811a62ce486ffaa8038ad86cb507a1363f318ab6fe3fbc2235c222c72a9ddaf1
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 c8a9a06a41d66dbb22fa230758c9dc7c80c45f70320b249a036ab7a07327ffc3
v0.45.1 · stable
### Fixed

- **Android: opkssh login no longer fails on the first try.** After
  confirming the OIDC login in the browser, the app used to show
  "failed to exchange token ... no such host" and you had to close
  the browser and log in again. Android freezes a backgrounded app's
  network while you are in the browser, and the token exchange fired
  into that frozen window. The app now keeps itself network-alive
  for the duration of the login (a "Signing in..." notification
  appears briefly when no sessions are open).

### Changed

- **ssh-tool is now licensed under the Apache License 2.0.** The
  LICENSE, NOTICE and contribution terms are in the repo root.

android-arm64 · ssh-tool-android-arm64.apk 19.9 MB · sha256 af89e1e231b6a8242f78a12a435a6fc66fccbd1b6f1aedc00fd799d514f81bc0
linux-amd64 · ssh-tool-linux-amd64 22.4 MB · sha256 6dc6f5b3c57d40d9d42c3c7ad93913509f9066b62820afe1b510bd6317eff879
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 660e535f4bf6386db5223bf155f512d98d8381ad4e6c37a0d91f7b5a46061290
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 c388ec9fe82d7e5f0ab207d2feb86f0dd0e1ae3482524d6b5a73d13bc8b6f221
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 fab1c74f2c99201eb056b8877409c3f5f45ef67263e641e76c912e39d16aa790
v0.45.0 · stable
The project now lives at
[github.com/fpenezic/ssh-tool](https://github.com/fpenezic/ssh-tool).
Releases are built by GitHub Actions and published both to
[sshtool.app](https://sshtool.app) and as GitHub Releases.

### Changed

- **Update checks now ask GitHub Releases first.** The in-app updater
  resolves the latest version, download URL, size, sha256 and release
  notes from the GitHub Releases API, falling back to sshtool.app
  when GitHub is unreachable or rate-limited. Nothing changes for
  older installs - they keep polling sshtool.app, which stays
  populated on every release. If you pointed
  `update_check_base_url` at your own server, that still wins and
  GitHub is skipped entirely.
- **Wails bumped to v3 alpha2.111.** Brings upstream stability
  fixes we care about: crashes on long-running Linux sessions under
  frequent asset loads (WebKit calls now complete on the GTK main
  thread), and a Windows crash when restoring the app after WebView2
  suspended during a long minimise. WebView2 component updated to
  1.0.27.

### Internal

- Release pipeline ported from GitLab CI to GitHub Actions; tags
  with a suffix (`-rc1`, `-test`) now land as GitHub prereleases
  for testing and never touch sshtool.app.
- Android: migrated to the new Wails mobile platform-manager API
  (`application.Android.*`) and the renamed `common:biometric`
  bridge event.

android-arm64 · ssh-tool-android-arm64.apk 19.9 MB · sha256 606169a19ad4be89e3101a3af4413840705bc67eb8edc95a2f61d82dc58b8efb
linux-amd64 · ssh-tool-linux-amd64 22.4 MB · sha256 942409a92d3481621e70f4dfa476c24ebe67a57727876aa3bdc337188fa5ad4e
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 2d3bac24b5529139e6a3a75e996ac7022ee396fe5945a500701b44e8d0eb9d5a
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 185d3f85a24fecd52b3ed5624b3d25069e2c748574468ceb0c171706fac8ac73
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 5c8c202bbb0a06a639971f0c86dcfb040bfd7af8da2e6a69c39ea0998713a58b
v0.44.0 · stable
## [0.44.0] - Edit existing port forwards

### Added

- **Edit a port forward.** Forwards could be created and deleted but not
  changed - a wrong address, port or description meant deleting and
  recreating. Each forward now has an Edit button that reopens the form
  pre-filled; Save applies the change. The kind (local / remote / dynamic)
  stays fixed - delete and recreate to switch that - and editing a running
  forward notes that it must be restarted to take effect.
android-arm64 · ssh-tool-v0.44.0.apk 37.6 MB · sha256 2e17cb671e37fe3b387102ff2a6c983914457e6951946d15c401fa86d8af709f
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 fcd728ab21a3fe8bdd7bcc3c71a8e4259bccb5939f48e5128543cd9855d20b87
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 c302e99d905b4f15e422deae492a5f954eb72263829376f51bf8f83915eebbc1
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 05c9e029c6108b7168e27ef892dd0c9ab176d0e02309c2b1e5c1219421fced9c
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 5ef4419b447962945a2a828ba4309ba76708afb8ec413c2fb9a82a17fcbf467e
v0.43.0 · stable
## [0.43.0] - Server status readout, scrollback search button

### Added

- **Optional server status in the status bar.** When enabled (Settings ->
  Terminal, off by default), the status bar shows load average, memory,
  disk and logged-in users for the SSH host of whichever pane is focused,
  refreshed every 10 seconds. It runs a small read-only probe (reads
  /proc, df, who) only for the focused session - not every open one - so it
  stays cheap even with many connections, and non-Linux hosts or network
  gear simply show nothing.
- **Scrollback search button.** The per-terminal scrollback search
  (Ctrl+Shift+F) now has a search button in the pane header that toggles
  it open and closed, so it's discoverable without the shortcut.
android-arm64 · ssh-tool-v0.43.0.apk 37.6 MB · sha256 529e63b61b2ae3481078b4324f94ac2ffcda1b956efbaa3098dbb22a7072283e
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 251252b720f119b2a004d52689182435666f9a64312cfca19a9c763f5ea22b25
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 6f056d24ef198ba9c2efc8db2afb60f4d368e154f09c1e0e6102b22d328ed65c
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 82b74c1a6a635df4c19534b58bcba5890d99857cb343362078efc3a2007ec89c
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 89f819f80c20b9b9107e17db0899e3bac3b100a588fed4a06eed0264ca42c575
v0.42.2 · stable
## [0.42.2] - Keepalive discoverability, scrollback note

### Changed

- **Keepalive is easier to find and understand.** The per-connection (and
  per-folder) keepalive field is relabelled "Keepalive / anti-idle (s)"
  with a tooltip and a short note explaining it sends a periodic SSH
  keepalive so an idle connection isn't dropped by a bastion or firewall.
  Blank inherits the folder's value, 0 turns it off. (The setting itself
  already existed and worked - it was just easy to miss.)
- **Scrollback setting notes the replay limit.** The Terminal scrollback
  hint now explains that while the live on-screen history goes up to
  100000 lines, only about the last 2000 lines replay after a tab is
  detached, re-docked, or the UI reloads.
android-arm64 · ssh-tool-v0.42.2.apk 37.6 MB · sha256 80c3f3bfd8d84c662d044d9fcec3208d81d7c37eeff8e4bdf13a722646f44cc3
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 4f4bb0371b79d855bbdc6e7e75ad2d049ca7b2e901ea71d5780fc806c769072f
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 80de6034b4986fb6bf911271329fe8614add6d36bd8e4141b286015667807bcb
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 d94f55cb8cb93a6927c781889856dd34a470de00c03aee29504b43699334910a
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 0c0bb3802a09645048704c3db05aecd267e5a1f09ae7a218b2c438b983426f98
v0.42.1 · stable
## [0.42.1] - Clearer port-forward and hostname labels

### Changed

- **Port-forward fields are labelled by kind, with hints.** The create
  form now names the listen side and the target side explicitly per kind
  (local vs remote), so a remote forward no longer shows a confusing
  "Remote host" field for what is actually the local target. Every address
  field has a mouse-over hint, and both default to `127.0.0.1`.
- **Connection "Hostname" is now "Hostname / IP address".** With a tooltip
  distinguishing it from the connection's display Name, so it's clear which
  field is the label and which is the address SSH dials.
android-arm64 · ssh-tool-v0.42.1.apk 37.6 MB · sha256 f808e1c45ccad59a985d9e804798e6da8ee77ce654fa4c511397ad299206f9ad
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 fa68534d2cf240723ef541db6aeeea6248e9366449e4d647b42416b57a0d8278
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 bbcd189b4b1cd504517a6f67723ddff2c89718d0bab3eb6db0912e568ba865a1
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 9d2b51ee0da76c753c31b96cc69e55e0c024ece297b4265c33cd47e059a99384
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 ac4b5edfa09736d249af7da314f9c1e5c08046b6d6be8d0d2a9f2ee3486907d4
v0.42.0 · stable
## [0.42.0] - Forward bookmarks, opkssh provider + cancel, broadcast fix

### Added

- **Edit SOCKS proxy bookmarks.** A bookmark on a dynamic (SOCKS5) forward
  can now be edited in place - a pencil button on each chip opens the
  inline form seeded with the current label and URL, instead of having to
  delete and re-add it.
- **opkssh provider picker.** The credential's opkssh config now offers a
  dropdown of the providers defined in your YAML, so you can pin which one
  to log in with and skip the chooser. Previously a config with
  `default_provider: webchooser` always opened the first provider (usually
  Google) with no way to choose another - `webchooser` is now flagged as
  unsupported with a hint to pick a provider explicitly.
- **Cancel a stuck opkssh login.** If an OIDC login never finishes - wrong
  config, or you close the browser - the connect used to hang until it
  timed out, with an app restart the only escape. There's now a Cancel
  button next to Connect while a connection is in progress.

### Fixed

- **Broadcast no longer leaks terminal escape codes into other sessions.**
  Running a full-screen program (vim, less) in a broadcast session dumped
  garbage like `^[[>0;276;0c` / `rgb:1111/...` into the other members'
  shells, where it ran as commands. Those were terminal report responses
  the remote program requested; only what you actually type is broadcast
  now.

### Changed

- **Port-forward bind field is labelled per kind.** For a remote forward
  the bind address/port field is now labelled "Remote bind address /
  port", with a tooltip explaining that `127.0.0.1` keeps the listener
  loopback-only and `0.0.0.0` exposes it. Default stays `127.0.0.1`.
android-arm64 · ssh-tool-v0.42.0.apk 37.6 MB · sha256 37f80a4efe3b2eb7485e559217305d899faab343123e4563b262df2ac64b85de
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 b853a6b25122128f0f8e7e2bc707ae08b981be6483be8358d5f0d504928c2b29
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 c84e36ab68ec306925ff035ce3801652a7dafd469e127e02e590448bc4ea30ae
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 57182615a53c6795bbc4037c4a0860e3eb563b4c64643d8ac7f9aa1b8919e46a
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 73d80630cafed327610a87fdc3d4351a94058fd03caed2741e4080c754f3f7d3
v0.41.1 · stable
## [0.41.1] - Stop auto-sync pushing on every inventory refresh

### Fixed

- **Auto-sync no longer pushes a new generation on every inventory
  poll.** A dynamic-inventory folder (Proxmox, Hetzner, etc.) stamps its
  last-refresh time on every successful pull, and that timestamp was part
  of the change signature that drives auto-sync - so the profile looked
  "changed" after each poll and pushed a fresh snapshot even when nothing
  you edited had changed (generation counter climbing on its own). The
  refresh bookkeeping is now excluded from the signature; a real change
  to a dynamic folder's configuration (or adding/removing one) still
  syncs.
android-arm64 · ssh-tool-v0.41.1.apk 37.6 MB · sha256 04793e55462e3404e2e9accba2ee9ef6dc6bd8eb290edd5fd0813ffcbc2027c4
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 db27e8c32901e3ba4fac945f511119fae5ede31f623eb1d85a2ce194c0c72764
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 672bd99e94fc6d6d76a0ecedccbbcee7423f556c9f913e36b7ac54303943c701
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 9cbd3d840edafd35c5d95b3efd066305c79a0d0d4b61cba54a64ddf999aaf7ea
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 3028d52f2e79ba575bde9eaabfadbfda37d1c716c5c1d1404a9846956aeddc52
v0.41.0 · stable
## [0.41.0] - Terminal render fix, VNC over jump hosts, copy toasts

### Fixed

- **Garbled terminal output on large colorized listings.** `ls -l` /
  `ll` of a big directory could render with the shell prompt landing
  mid-listing, lines duplicated or out of order (pressing Enter
  redrew it). The cause was output arriving out of order: each chunk
  carries a cumulative byte counter, but the events that deliver them
  were dispatched on independent goroutines and could overtake one
  another. Output is now reassembled strictly in order on the cumulative
  counter before it reaches the terminal, and the backend drives it
  through a single stream, so a big listing always renders correctly.
- **VNC console froze the whole app.** Opening a console could lock the
  entire UI (you couldn't even close the window). A reactive loop in the
  console-controls wiring spun the render thread; that path was
  reworked so it can't happen.
- **VNC console through a jump host.** A console on a connection with a
  jump host failed unless you were on the VPN - the direct path dialed
  the VNC host straight from your machine, ignoring the bastion. It now
  routes the RFB connection through the jump host, the same as SSH.
- **VNC failures now say why.** A console that couldn't reach its target
  used to show only "Connection closed unexpectedly". It now reports the
  actual reason - jump-host login rejected, host not found, connection
  refused, or timed out.

### Added

- **"Copied" toast on copy actions.** Copying a hostname, port, public
  key, password, terminal selection, etc. now shows a brief
  confirmation. Password copies note the 30-second auto-clear.
- **VNC connect feedback.** While a console connects it now tells you how
  it's reaching the host (direct, via a named jump host, or over an SSH
  tunnel) and gives up with a clear message after a timeout instead of
  spinning forever.

### Changed

- **VNC console controls moved into the tab header.** Status and the
  Fit / Dot cursor / Ctrl+Alt+Del / Reconnect buttons are now inline in
  the header instead of a separate toolbar row, giving the console back
  that vertical space.
android-arm64 · ssh-tool-v0.41.0.apk 37.6 MB · sha256 31231b8c546265239d1793e4033b6cd6875d1656bfd3cf035bf5f569669a4973
linux-amd64 · ssh-tool-linux-amd64 22.3 MB · sha256 66dfc7cb62199242dffea9fae98830529f880eeeff0b2af6bb411ad0d2380d14
linux-arm64 · ssh-tool-linux-arm64 21.0 MB · sha256 c7a78a21687cac797886f1ab2be3a92799d6956ddfb72015e52cffe4d38f4870
windows-amd64 · ssh-tool-windows-amd64.exe 22.8 MB · sha256 8a00b96ea0bbf1a7bd25d2aa2bccdcd30ae3804f11ceff0f4fed045e22807318
windows-arm64 · ssh-tool-windows-arm64.exe 21.2 MB · sha256 8b807d24d310415605ff96b55bb57c5d6f66a34ac8ca50518376edef0f8e3001
v0.40.1 · stable
## [0.40.1] - Sealing robustness

### Fixed

- **Backup / sync sealing no longer fails when USER isn't in the
  environment.** The machine-bound seal derived part of its key from
  the USER / USERNAME environment variable and errored outright when
  neither was set (some containers, cron, CI). It now falls back to the
  OS account (and finally a constant), so backups and sync snapshots
  seal everywhere. Existing auto-unlock sidecars keep working.
linux-amd64 · ssh-tool-linux-amd64 22.2 MB · sha256 0e9fb5e6fc3994a12abfcd6907effc3fffa0eeafe863e71702c8aae2039f8c58
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 395b19f1870ae7684096a533de8d9bd9046330f68c5f6d157a28ce6f046bc241
windows-amd64 · ssh-tool-windows-amd64.exe 22.7 MB · sha256 abada936f7a6943ba56241018a39a237f8f66e67bd6220c100bf057a102a1054
windows-arm64 · ssh-tool-windows-arm64.exe 21.1 MB · sha256 93c4e1630e9ad044a5d24e3f412c66fce8294b28a6b27f713b0e6269d8a7cc6e
v0.40.0 · stable
## [0.40.0] - SFTP sync

### Added

- **Sync over SSH/SFTP, not just WebDAV.** Settings -> Sync now has a
  transport choice: keep using a WebDAV server, or store the encrypted
  snapshot in a directory on any SSH server you have. The snapshot is
  still sealed locally (argon2id + XChaCha20-Poly1305) before upload, so
  the server only ever sees ciphertext. SFTP uses an atomic POSIX rename
  for the snapshot swap.
- **SFTP auth, two ways.** Reuse a credential from your vault tree
  (key / password / opkssh - convenient on a machine that already has
  it), or type the auth in directly (password or a pasted private key).
  The inline option is what lets a brand-new machine bootstrap: a vault
  credential wouldn't exist there until the first pull brings it in. The
  host key is verified the same way as any other connection.
linux-amd64 · ssh-tool-linux-amd64 22.2 MB · sha256 552cb302cddd7c0e30aabea4de9a88949bf970b698adca9476cbc3b8b9d9f808
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 ac7cf051132f28b34ae854169888603421c93b0cb7e13ee1ae0482128f34437f
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 76d178bbf3a8fe5e78383c30ae3f034664d5354d5e8f250852c7e15a19806ace
windows-arm64 · ssh-tool-windows-arm64.exe 21.1 MB · sha256 3b1eb296979fc5777a2238c7ffa7aae5752a99fdb3ab8ec6e1417ce27605f113
v0.39.0 · stable
## [0.39.0] - Android deep links

### Added

- **`ssh-tool://` deep links work on Android.** "Open in ssh-tool"
  import links (and QR codes) now open the app and run the import, the
  same as on desktop. Cold and warm launches are both handled
  (`launchMode=singleTask`). The Settings URL-scheme registration
  control stays desktop-only - on Android the scheme is bound at
  install time by the manifest, nothing to register at runtime.

### Fixed

- **Android App info no longer shows "Version 1.0".** The gradle
  `versionName` / `versionCode` were the scaffold defaults; they're now
  injected from git at build time, so the OS-level App info matches the
  in-app version.
android-arm64 · ssh-tool.apk 37.5 MB · sha256 3e33eb905764ce866888d85af1ac7e1e28b0c938265fb1aaca4eee33c7b21421
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 4fc89e794afd03c90eda74f01b804e2ebabbae4906756271372e84360e825612
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 d19affc1ac6be9b9c1c2c2b613311661c2795c1944a892990f61fbb795222611
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 d7a2f3f226b274c2af33d9d0e4d1923b3f31f43bc355e7d628a44bedaba5f905
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 f72b4651d0750fb1de6941ab2db46707ebae51279ab977b04c97c687a39611e8
v0.38.1 · stable
## [0.38.1] - Android version stamp

### Fixed

- **Settings -> About showed "dev" on Android.** The android build never
  injected the version into the binary (unlike the desktop builds), so it
  fell back to the `dev` default. The git-describe version + commit are now
  stamped in.
android-arm64 · ssh-tool.apk 52.3 MB · sha256 ae0b6b2f55b66b1591c01dcefa0da92aa8cc3e00ca48f8b2718c718f68030abe
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 3601d46969287dcfc5cee115e40d1d8a0d8ae62628747baaf1e96a7de0b3c4b3
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 b026dd6a080b46cc758be7b2506e9fcbc3ae74476d50924104ffc0b38a48dc80
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 571e07673ebff2535adbaa69e4b7f8f8d3afc5263a6bb09429d0f3427dda17f7
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 b76a759614849786038343610e137d49d8a7665c65503aefde71ca5600c13ba4
v0.38.0 · stable
## [0.38.0] - Android app id

### Changed

- **Renamed the Android app id off the Wails scaffold default**
  (`com.wails.app` -> `app.sshtool`). Only the installed identity moves;
  the internal package stays `com.wails.app` because the Wails runtime
  hardcodes its JNI exports there. **This installs as a new, separate
  app** - it does not upgrade an existing `com.wails.app` build in place
  and starts with an empty profile. Migrate by sync-pulling into the new
  install, then remove the old one.
android-arm64 · ssh-tool.apk 37.6 MB · sha256 89a21c265c4c60d47ace4d32a304f48b7d20edd8be9e7e0ccc70daf87e7130e9
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 db6210cd7ca22b3c40873d4b94e925aaf599dd94fc62b30543aefdbe3627650c
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 026afc4698e0fe1258a61735831d178c0bb4f480f558c073a0bb50e0a340ecac
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 b2beac587203bb730dfc8fa4ba00a9bcb5a3468ba53131b2d6cc413515501d51
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 3609ca3cab66eb0d6ea6f2bd90dfe0eb39804a9f9ad05ccd87b4f6e31d121ef1
v0.37.1 · stable
## [0.37.1] - Android folder long-press

### Fixed

- **Folder long-press on Android no longer navigates away.** Opening a
  folder's context menu used to also select the folder, which on mobile
  slid in the folder settings pane underneath the menu. The long-press
  now just shows the menu; a new "Folder settings…" item is the
  deliberate way into the settings pane.
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 656bef3024813683c3093ef4bb0207fa11445fcbe71cbcf750eecef8df48b1d0
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 f51d8db4b12b3daecdb14a865fbb5d2c6917f8cd9b42be3756757be134b23b58
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 acbb3c2da0ba0e3bf4e04c7cdb3931ac9e681cb458d87711941031321ffc742e
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 48c21777facb0682b57721a01f82b24b0bef55b45385ec5a0f02923d8d47c52a
v0.37.0 · stable
## [0.37.0] - Android UX fixes

Live-testing follow-ups on the Android app. Desktop is unaffected -
every change is gated behind a build tag or an `isMobile` runtime check.

### Fixed

- **opkssh login now opens the browser on Android.** The OIDC flow used
  to shell out to `xdg-open`/`open`, which don't exist on android, so
  the login silently never started. The login URL is now routed to the
  system browser via an `Intent.ACTION_VIEW` through the JNI bridge
  (new `WailsBridge.openURL` + a `SetOpenBrowserOverride` hook on the
  opkssh provider). The loopback callback still lands back in-process.
- **Sync pull no longer demands a restart on Android.** There is no
  machine-bound sidecar on android, so a live pull always fell back to
  "restart to apply passwords/keys" - and the Restart button (a desktop
  process re-exec) did nothing on a phone. The pull now reads the vault
  passphrase from the Keystore-backed secure store (when biometric
  auto-unlock is on) and merges the pulled vault in place, so it applies
  live. In the rare auto-unlock-off case it shows a clear "close and
  reopen the app" instruction instead of a dead button.
- **System Back steps through the UI instead of exiting.** Android Back
  (button or gesture) now goes detail -> list, or a secondary tab ->
  connections, by arming a synthetic history entry that `WebView.goBack()`
  consumes. It only exits the app at the root.
- **Folders are easier to expand on a phone.** A plain tap on a folder
  row toggles it (the chevron alone was a tiny touch target and
  double-tap was awkward); rows and the chevron hit area are larger on
  touch screens.
- **Dynamic-inventory entries open their preview on tap.** Tapping a
  dynamic VM/host now slides in its detail pane on mobile (the
  single-pane view previously ignored dynamic-entry selection), and the
  dynamic Connect button shows a "Connecting…" busy state so a slow
  connect doesn't look like a no-op.
- **Hid the desktop-only ssh-tool:// handler registration on mobile.**
  Registering an OS URL handler isn't wired on android; the control
  used to surface its failure as an error. Pulling an archive from a
  URL still works.
android-arm64 · ssh-tool.apk 66.2 MB · sha256 ac5ae7cb482399b7eb0270572b459d2e7f1391e0a31a745fbbd258aaaf627ae9
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 162d693f5f2c6045af5a0797b00ebcf4b306beda81d1adda21433ed0d2fa8b2d
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 1e669ca5519ffba2429eb689180333e3f2ced637a92a6f9994a809ae72bdce04
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 20afeedfc005ee0c2cfd933eb88ce25e7eea5fdab5da9ff97ad33f089fc35595
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 aa509c99e634026dff246cf755dce1592cf27ee27d134a1b565bd1afb2f96b9d
v0.36.0 · stable
## [0.36.0] - Android / mobile app

ssh-tool now runs on Android (and the same code path is ready for iOS).
The desktop build is unchanged - everything mobile is gated behind build
tags and an `isMobile` runtime check, so the Windows/Linux/macOS app
behaves exactly as before.

### Terminal rendering fix (desktop + mobile)

- **No more "hieroglyph" corruption.** Garbled / overlapping glyphs that
  occasionally appeared after a font-size change or when toggling
  broadcast were a stale WebGL glyph-atlas. The atlas is now cleared on
  every font-size change (Ctrl+wheel) and on any broadcast-membership
  change, so the next paint always re-rasterises cleanly. On mobile the
  WebGL renderer is off by default (the Android WebView's WebGL atlas is
  flaky); the DOM/canvas renderer is used instead.

### Password fields

- **Show/hide toggle on every password field.** The eye icon that already
  existed on a couple of fields now appears on all of them - vault
  unlock/create, connection and credential passwords, sync and
  import/export passphrases, API token secrets, VNC credentials.

### Android (new)

- Boots as a real app ("ssh-tool" with its own icon), unlocks the vault,
  manages connections, and opens SSH sessions with a full terminal - all
  on the same Go core and Svelte UI as the desktop.
- **Biometric vault auto-unlock.** The passphrase is stored in the
  device's hardware-backed keystore (EncryptedSharedPreferences) and
  unlocked with fingerprint / face. Opt in with the checkbox on the
  unlock screen; falls back to the typed passphrase.
- **Sessions survive backgrounding.** A foreground service (with an
  ongoing notification) keeps SSH sessions connected while the app is in
  the background, instead of the OS suspending the process and dropping
  the sockets.
- **On-screen key bar.** A soft-keyboard accessory row supplies the keys
  a phone keyboard lacks - Esc, Tab, Ctrl/Alt (latching), arrows,
  Home/End/PgUp/Dn, pipe/slash/tilde/dash, and ^C/^D/^Z/^L/^R/^A/^E/^U/
  ^K/^W control combos.
- **Mobile layout.** The two-pane tree+detail view collapses to a single
  pane with a Back affordance; the top nav compacts to icons; Settings
  stacks to one column and hides desktop-only options (local shell,
  external terminal, tray, browser launcher, copy-paste modes). Two-finger
  pinch in the terminal adjusts the font size.
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 51bd3598ee9c4e699e4c18971a38bb23646edbe8ee4d81d013cc3b20939e0ac8
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 edf8c02578674ab4398ebfed2d766bb5f9c874e26339e8ad77abb680f536eb95
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 92e0fcd7567500fb4ec4be23757dee40df15e1d9d67f8e3d66f460f1c406113f
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 30f93a3b472e606f0b99f32bbe9dcbbe75a63588072c7ebee022d3b78d6da084
v0.35.1 · stable
## [0.35.1] - VNC polish + text-selection UX

- **Clipboard sharing between your machine and a VNC console.** Ctrl+V /
  Cmd+V puts your local clipboard onto the remote (then paste with the
  remote's own shortcut); copying in the remote mirrors back to your
  local clipboard. Read and written through the OS clipboard natively,
  since the webview blocks clipboard access over the console canvas.
  Whether the remote adopts an incoming clipboard is up to its VNC
  server - x11vnc / TigerVNC / Proxmox guest consoles honour it; macOS
  Screen Sharing largely ignores incoming clipboard, so paste-into-a-Mac
  is unreliable on that server.
- **Cursor no longer disappears.** Some servers (macOS Screen Sharing)
  send no pointer shape until the screen changes, leaving the cursor
  invisible at first. A dot cursor is now always drawn; toggle it off
  from the toolbar if the server draws its own.
- **VNC console on pinned Proxmox connections.** Pinning a Proxmox guest
  to a permanent connection keeps its console: "Open VNC console" routes
  back through the Proxmox API (it re-finds the guest's node by vmid), so
  you get the real console, not a generic VNC dial.
- **Numpad Enter works in Proxmox consoles.** It was being sent as a
  keypad-Enter the LXC/serial text console ignored; it's now a plain
  Return.
- **The app no longer text-selects like a web page.** Dragging across the
  UI doesn't paint a selection and Ctrl+A doesn't grab the whole window.
  Selection is kept where copying matters - logs, the audit table, code
  and path blocks, the terminal, notes. In the log viewer and audit
  table, Ctrl+A selects just those rows. Inputs are excluded so a
  selection started in a log can't sweep up a nearby field's text.
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 3c33cad2ae66627a7e93d9ac87b0ea0822a26d5368f9f26d5f2bfb8d982dc3c0
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 ddaf24d962730a2de9ded3ea4523a17b0fc02e7e81de8481f27279ed6b3f3fed
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 7f988417791f2665192b834188607390cf16a39387a0781dcea17322c92489b0
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 3e27c2108109176b603253e936f607ea9e4dfc6db396a8260f347f9f7efe793b
v0.35.0 · stable
## [0.35.0] - VNC console: Proxmox + generic, in-app noVNC

- **See a remote desktop or VM console in a tab.** A new VNC console
  renders inside the app via noVNC - no external client, no X server,
  no separate install. It opens as a regular tab alongside terminals
  and local shells (locked to a single full pane - a desktop wants the
  whole tab, so VNC tabs don't split or swap to SFTP). Detaching the
  tab to its own window and redocking works like any other tab.
- **Proxmox VM / LXC console.** Right-click a Proxmox guest (in the
  inventory or its open tab) and pick "Open VNC console" to get the
  guest's screen. It reuses the folder's API token - the app drives
  Proxmox's vncproxy + vncwebsocket for you, honouring
  `insecure_skip_verify` for self-signed clusters. Works for QEMU VMs
  and LXC containers.
- **Proxmox node (host) console.** Hosts work too, but PVE refuses API
  tokens for a node shell, so it needs a real login: set a "VNC console
  login" on the Proxmox dynamic folder (a password credential whose
  name is your PVE user, e.g. `user@ldap`). The app logs in for a
  ticket and opens the node shell.
- **Generic VNC over your own hosts.** Turn on "VNC console" for any
  connection (it's off by default - most SSH hosts have no VNC server).
  Set the RFB port (default 5900) and choose to dial it directly or
  tunnel it through the connection's SSH session (reaching a
  localhost-bound x11vnc / TigerVNC / macOS Screen Sharing on the
  remote's loopback). The login pre-fills from the connection; an
  optional VNC password lives in the vault, and noVNC prompts in-panel
  if the server still needs one (e.g. macOS Screen Sharing).
- The console toolbar has fit-to-window scaling, Ctrl+Alt+Del, and
  reconnect. Pixels flow over a loopback bridge inside the app (no
  secrets in the URL); the webview never needs custom headers or
  TLS-skip.
- **Sync is now schema-skew tolerant.** A live pull between machines on
  different app versions only copies columns both sides have, so a
  newer version's additions (like the VNC fields) never break an older
  version's pull. Unknown settings ride along untouched.
linux-amd64 · ssh-tool-linux-amd64 22.1 MB · sha256 89089136972c279e636bb2dd9cab8ce0fb93c25e0ae408ea64a828c621d14a88
linux-arm64 · ssh-tool-linux-arm64 20.8 MB · sha256 dcac76c3b1db56c55c6d94b83b92e8df0541f44036961ce13509f6635bd7d04f
windows-amd64 · ssh-tool-windows-amd64.exe 22.6 MB · sha256 ff439fbf5d55f81e901e2830804e8c40be4df0daa2658e33fd816ceef6cd1875
windows-arm64 · ssh-tool-windows-arm64.exe 21.0 MB · sha256 620956756ed44016b829f0b5bc048a2896906b4c30e7d11591fe326d4ea46cc2
v0.34.0 · stable
## [0.34.0] - live sync pull, settings polish, htop fix

- **Pulling a synced profile no longer needs a restart.** The pulled
  profile is applied straight into the running app: the store is
  mirrored into the live database and the vault's secrets are
  re-encrypted under this machine's key and merged into the unlocked
  vault. SSH sessions stay open, the UI just refreshes. When the
  status-bar pull pill (or its toast) reports another machine has
  newer data, one tap applies it - no confirm, no restart.
- This machine's own state is never overwritten by a pull: window /
  session layout, "recently connected" order, and the sync
  configuration (server, passphrase, generation) are preserved while
  the rest of the profile is replaced.
- The vault merge uses this machine's auto-unlock passphrase, so for a
  single user with the same vault passphrase on every machine it's
  fully silent. In the rare case the machines use different vault
  passphrases, the connections apply live and only the passwords/keys
  fall back to a one-click restart - never a passphrase prompt.
- **Apply incoming changes automatically** (opt-in, under Settings >
  Sync > Automatic): when another machine pushes a newer version and
  this one is clean, the change is pulled and applied in the
  background - but only while the UI is idle (nothing focused, no
  dialog open), so a pull never rearranges the tree out from under
  you; otherwise it waits for an idle moment or leaves the
  notification.

### Settings

- The whole **Sync** page is reorganised into clear sections (Server /
  Status / Manual sync / Automatic) with plain descriptions instead of
  one dense block.
- **Wide-window layout fixed**: the sidebar now runs full height
  (it used to stop short and leave a blank strip), section cards are
  centred at a readable width instead of floating in empty grey, and
  nav / labels / hints read with more contrast. Settings honours your
  chosen UI font size.

### Terminal

- **Fixed htop / btop ghosting**: full-screen TUIs that repaint every
  tick could leave the WebGL renderer compositing old and new cells
  (header painted over the process list). The glyph atlas is now
  cleared on resize, with **Ctrl+Shift+L** as a manual force-redraw
  for the rare in-place case (plain Ctrl+L stays the shell's
  clear-screen).

### Other

- **Connection-folder editor** now shows unsaved changes (Save * /
  Saved) like the connection editor, and no longer double-toasts on
  save.
- Updated to **Wails v3 alpha.101**, picking up upstream fixes to the
  Windows self-updater (cross-volume temp dir), the macOS screen-info
  crash on display changes, and a Linux asset-server crash.
linux-amd64 · ssh-tool-linux-amd64 21.7 MB · sha256 e112eabda44f3bed1ee8e9d9d5cc804bedec1293bad2083641a1dafef1e029fb
linux-arm64 · ssh-tool-linux-arm64 20.5 MB · sha256 ecb8f904e98b2f89f29b43b9fc308c2b4e15ac987095afb5c0c2320c362ce05f
windows-amd64 · ssh-tool-windows-amd64.exe 22.2 MB · sha256 da44cb56d16ead07ea320b34d73587d3a73e2be4e3e1845bbabca36095d4b30b
windows-arm64 · ssh-tool-windows-arm64.exe 20.7 MB · sha256 c2071774ac61bf1ed508352398748d01fba0014dd3848d6849245387477fa987
v0.33.2 · stable
## [0.33.2] - sync: no phantom push on the first launch after an update

- **Updating the app no longer creates a phantom "update to pull".**
  The sync change signal is a fingerprint string whose internal format
  can shift between versions; the first launch on a new build read the
  old fingerprint as a change and pushed an otherwise-unchanged
  profile, which the other machine then flagged as something to pull.
  The fingerprint now carries a format tag and a format-only difference
  re-baselines silently - no push. A genuine offline edit still pushes.
linux-amd64 · ssh-tool-linux-amd64 21.7 MB · sha256 956573282e13aaeefa0708cfd3ca1fa3230bc5c39552e09c0dbbacde8269f19f
linux-arm64 · ssh-tool-linux-arm64 20.4 MB · sha256 c9feb3b2bd61d4a02107692aa363bc203bc8ea4e09980af0f548ecb44edeb48f
windows-amd64 · ssh-tool-windows-amd64.exe 22.2 MB · sha256 c78ccd15cfa7a2d9f6e369f855b79edf97d6dab9d2fb8f4e33ae609d7bac4b7e
windows-arm64 · ssh-tool-windows-arm64.exe 20.6 MB · sha256 459d527aa6dde5871056c07dfe26530be51628657a34dc713716cf14a11d8757
v0.33.1 · stable
## [0.33.1] - sync polish: no idle pushes, machine-local state, one-click restart

Follow-up fixes to v0.33.0 from two-machine field testing.

- **No more spurious auto-pushes.** Auto-sync was pushing every few
  minutes (and on every launch) with nothing actually changed. The
  change signal is now a content fingerprint of the profile tables -
  stable across restarts and across machines, moving only on a real
  edit - instead of file mtimes (which a WAL checkpoint bumped) or a
  per-session counter (which reset each launch). Several writers that
  were dirtying the profile without a user change are split out or
  silenced: the audit log moves to its own machine-local database,
  "recently connected" timestamps and the open-tab / window-geometry
  state become machine-local, dynamic-inventory refreshes no longer
  rewrite an unchanged host list, and provider tags now have a stable
  order (a randomised Hetzner tag order was the main culprit).
- **One-click restart after a pull.** Pulling a profile (which applies
  on the next start) now offers a Restart now button that relaunches
  the app for you, instead of a missable "quit and reopen" toast.
  Relaunch is fast again - the parent-exit wait no longer stalls on
  Windows.
- **Sync only carries the profile.** Open tabs and window position are
  per-machine now and no longer travel in the sync snapshot, so a pull
  doesn't rearrange the machine you pulled onto.
- **Settings text larger on Windows.** The Settings panel was cramped
  at the default 13px UI base; its sections render ~8% larger now.
linux-amd64 · ssh-tool-linux-amd64 21.7 MB · sha256 5e2d6525105bc9e25fd80d3ad75ad8849c438d8122b6bbe1a8df405e787beb52
linux-arm64 · ssh-tool-linux-arm64 20.4 MB · sha256 b17e9073531b8ce61f5cf1e4e11708a4c5f20828066bea4a644f5142b6afe70e
windows-amd64 · ssh-tool-windows-amd64.exe 22.2 MB · sha256 359330e0a31d25e6d08ab973b058ed0c2552c262b4a9c048a4b536367fb9cdde
windows-arm64 · ssh-tool-windows-arm64.exe 20.6 MB · sha256 d65a42c28ed6d8f5f8dd11b44ec75c22289b4dde72a55f556c74bd73c3933b25
v0.33.0 · stable
## [0.33.0] - encrypted profile sync over WebDAV

- **Sync your whole profile between machines** via any WebDAV server
  you control (Nextcloud, Apache mod_dav, rclone serve webdav).
  Everything travels: connections, folders, credentials AND their
  secrets (the vault), custom icons, settings, snippets, workspaces.
  Setup under **Settings > Security > Sync**.
- **Encrypted before a single byte leaves the machine.** The snapshot
  is the same sealed envelope backups use (argon2id +
  XChaCha20-Poly1305), locked with a sync passphrase that is
  independent of the vault passphrase. The server stores ciphertext
  plus a tiny meta file with a generation counter - a compromised
  WebDAV host learns nothing. HTTPS is required (plain http only for
  localhost); certificates verify against the OS trust store, so a
  private CA installed system-wide works. No skip-verify toggle, by
  design.
- **Snapshot semantics, git-like guard.** Push uploads this
  machine's profile; Pull replaces this machine's profile (staged
  like a backup restore - pre-restore safety copy kept, restart to
  apply). Push is refused when the remote has changes this machine
  hasn't pulled; Force push overwrites deliberately. No row-level
  merging - a vault cannot be half-merged.
- **Auto sync** (opt-in): pushes ~90 seconds after the last change
  and best-effort on quit (10s cap so a dead network can't block
  exit), and checks the server every N minutes plus at startup -
  wake the laptop from standby and within a minute a toast + a
  pulsing "pull" pill in the status bar tell you another machine
  pushed. Conflicts are never auto-resolved: auto-push steps aside
  when the remote is ahead.
- **One-click pull.** Clicking the toast or the status-bar pill
  pulls right there when this machine has no unsynced changes
  (lossless); with local changes pending it opens Settings > Sync
  so you decide with the full picture.
- **New machine setup**: enter the WebDAV details + sync passphrase,
  Pull, restart, then unlock once with the vault passphrase from the
  source machine (the machine-bound auto-unlock re-arms itself after
  that first unlock).
linux-amd64 · ssh-tool-linux-amd64 21.7 MB · sha256 e91a77c4318256b098955bac0c784f5f5996c621b65a007a4425a493857c040b
linux-arm64 · ssh-tool-linux-arm64 20.4 MB · sha256 4960b34f2e451a6cc2621bd1ee1da73933da954d035b970f3b71d88d128998a6
windows-amd64 · ssh-tool-windows-amd64.exe 22.2 MB · sha256 a5e7d5a3c4d3003233e7fb695c970d98055f1297de214e3e6ccc1fc8722731c9
windows-arm64 · ssh-tool-windows-arm64.exe 20.6 MB · sha256 3695209f492652f5c67a1f7e6342519865dd07113d41c6355f3f1605cda7bc6a
v0.32.0 · stable
## [0.32.0] - import hub, archive completeness, creds tree parity

### Import

- **MobaXterm import.** Load a `.mxtsessions` export (right-click
  User sessions > Export in MobaXterm): SSH sessions become
  connections, the bookmark folder tree is rebuilt (existing folders
  reused on re-import), RDP / telnet / VNC entries are counted and
  skipped. MobaXterm's export carries no passwords - attach
  credentials afterwards.
- **PuTTY / KiTTY import.** PuTTY has no export button; run
  `reg export "HKCU\Software\SimonTatham\PuTTY\Sessions" file.reg`
  and load the file. Only `Protocol=ssh` sessions import,
  `user@host` hostnames split into username + host, UTF-16 `.reg`
  files (the `reg.exe` default) are handled. PuTTY stores no
  passwords, so nothing is lost in the move.
- **One Import page.** The three import sections merge into a
  single **Settings > Import** with a source picker: ssh-tool
  archive (default - also hosts the ssh-tool:// handler
  registration), ssh_config, PuTTY/KiTTY, MobaXterm, Devolutions
  RDM. Every import stays additive and safe to re-run.

### Export archive

- **Per-connection passwords now travel.** A password set directly
  on a connection (no credential entry) was silently dropped by
  export. With "include credentials" on it now ships sealed in the
  same encrypted block, and import restores + relinks it - onto
  rows the import created or overwrote, never onto skipped ones.
- **Custom icons now travel.** Folder / connection / credential
  icons ride in the archive (deduplicated by content on import, so
  a logo shared by 200 connections costs one blob). The export
  modal gets a "Custom icons" strip toggle alongside notes / tags /
  color.

### Credentials

- **Cascade delete, properly.** Deleting a credential folder used
  to ask for a typed DELETE and then dump its credentials flat at
  the root. It now shows the same staged confirm modal as the
  connections tree - listing the folder, every credential inside
  and all subfolders - and deletes exactly that, with vault secrets
  and sealed history cleaned up per credential.
- **Folder multi-select.** Ctrl/Cmd+click toggles credential
  folders in and out of a multi-selection, Shift+click ranges
  across the visible list - the same gestures connections (and
  credentials themselves) already had. Delete takes the whole
  selection.
- **Save feedback parity.** Key and password rotation now toast a
  confirmation like every other save in the credential editor.

### opkssh

- **Cert status at a glance.** The credential editor shows what's
  in the vault: "Certificate in vault: issued 2h ago - re-login in
  ~6d22h" (or "re-login on next connect" once overdue). Read-only -
  never triggers a refresh itself.
- **Human-friendly durations.** Max cert age and refresh threshold
  accept free text: 7d, 6d23h, 48h, 90m, spaces allowed. The live
  hint shows exactly what will be saved (storage stays whole
  hours / minutes).

### Fixed

- **macOS: Cmd+Q skipped the quit prompt.** Application-level quit
  bypassed the window-close hook and killed live SSH sessions with
  no warning. Quit now routes through the same "N active sessions
  will be disconnected" confirmation on every platform.
- **Windows: Task Manager name regression mine.** The build-assets
  template still carried the long app description; one regeneration
  would have reverted the v0.29.2 fix. (If Task Manager still shows
  the old name on an updated install, that's Windows' MuiCache for
  the exe path - the binary itself is correct.)
linux-amd64 · ssh-tool-linux-amd64 21.6 MB · sha256 1eb5e8c35f0f86b67368c25041077bf77a03d72276e14a2283dcd1e781a6a4d2
linux-arm64 · ssh-tool-linux-arm64 20.3 MB · sha256 23b602a3c076729a71bc00eda70a38a78fa0352ff952271f93f624252069c439
windows-amd64 · ssh-tool-windows-amd64.exe 22.1 MB · sha256 0f530fd897fb6a50e067a96f7a3ea944a62e63409126b517d08e7339658ff628
windows-arm64 · ssh-tool-windows-arm64.exe 20.6 MB · sha256 c02658dc0ea9209c747f3a4d8f28d963d1765fca70a9d1b635cabb3d5b635d67
v0.31.0 · stable
## [0.31.0] - session recording + built-in player

- **Record any session to an asciicast v2 file.** Right-click a
  terminal tab > **Record session** (acts on the active pane; in a
  split tab each pane records separately), or use the palette
  command **Record / stop recording session**. A pulsing red dot
  marks recording tabs. Works for SSH and local shell sessions;
  closing the session finalises the file - it is never discarded.
  Files land in `<data dir>/recordings/` as
  `<connection>-<timestamp>.cast`; the folder is configurable under
  **Settings > Terminal > Session recording**. Start, stop and
  delete are audit-logged.
- **Output only, by design.** Keystrokes are never written to the
  file, so a password typed at a sudo prompt cannot leak into a
  recording. There is intentionally no option to record input.
  Mid-session resizes are recorded so playback reflows exactly
  where the live terminal did.
- **Built-in player.** **Browse session recordings** in the palette
  (or Settings > Browse recordings…) lists every recording with
  date, duration and size. Playback runs on the same terminal
  engine, font and theme as live sessions: play/pause (Space, or
  click the terminal), seek scrubber, 0.5-4x speed, and **Skip
  idle** - gaps longer than 2 s are jumped, so a half-hour capture
  plays in the time the output actually took. Fit-to-window font
  scaling keeps large recorded grids visible; a fullscreen toggle
  gives them the whole window. Text selection and copy (Ctrl+C /
  right-click) work, including inside htop/vim recordings. Files
  are standard asciicast v2, so `asciinema play` works too.
- **Fixed: quit prompt could hide behind a modal.** The "Quit
  ssh-tool?" confirmation rendered underneath the recordings player
  - it now sits above every other layer.
linux-amd64 · ssh-tool-linux-amd64 21.6 MB · sha256 c078fafcadadc799428934e048c295596c41a1ad2edb25ee6fef469c085dd6b1
linux-arm64 · ssh-tool-linux-arm64 20.3 MB · sha256 46873a0aa2eeb260696bfc316f6c21dd13038f68bfcf1785506889afd1ebcbaf
windows-amd64 · ssh-tool-windows-amd64.exe 22.1 MB · sha256 4406e1cedd9a1dceff359d38f100530bda20fdb678e21295bc0e8f72a335d1c8
windows-arm64 · ssh-tool-windows-arm64.exe 20.5 MB · sha256 64bfc779b8749b83917bdb9c80fabde994265a0380ed9d25261e277babe8e266
v0.30.1 · stable
## [0.30.1] - export archive completeness

- **Credential folders survive export/import.** Imported credentials
  used to land flat at the root of the credential tree - the archive
  carried each credential's folder reference but not the folder
  hierarchy itself. Archives now include the credential-folder tree
  and the importer rebuilds it; re-imports reuse existing folders
  (matched by name + parent) instead of duplicating them. Old
  archives still import, just flat - re-export to pick up the
  structure.
- **Dynamic-inventory folders survive export/import.** A Proxmox /
  Hetzner / ... folder used to import as a plain empty folder: the
  provider config wasn't in the archive, and the API-token credential
  it references was never collected for export. Both travel now -
  config (provider, base URL, refresh interval) on the folder, token
  credentials included when "include credentials" is on, with all
  references remapped on import. Cached host entries stay out of the
  archive; the first refresh on the importing side repopulates them.
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 419ac408716fe62c3025ac707a8c8bc9ae93f628997742ee1b051a23b98d9268
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 0075e475d50d72752694c1f66144dd060b6c30848ddb836f6f05d43abae94cb1
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 39dce7f96093b68a6fec2c55cfe703962ff4cc939f44142fc33b6ad9eb67decf
windows-arm64 · ssh-tool-windows-arm64.exe 20.5 MB · sha256 41631ef14bbdf503dfe427318f1b71fdbf897da58120f7b71d7155894c8819c8
v0.30.0 · stable
## [0.30.0] - macOS catch-up

- **ssh-tool:// links work on macOS.** The handler used to be a stub
  that errored (and showed the error as raw JSON). The .app bundle
  now declares the scheme the way macOS expects (Info.plist), the
  Register button forces a Launch Services re-index when needed, and
  the app listens for the Apple Event macOS uses to deliver opened
  URLs - they never arrive as command-line arguments, so the old
  argv-based path couldn't see them at all.
- **Vault auto-unlock works on macOS.** The machine-bound key
  derivation had no macOS identity source (machine-id is a Linux
  file, HOSTNAME isn't set for Finder-launched apps), so the sidecar
  could never be written. It now uses the hardware UUID
  (IOPlatformUUID) - the platform's stable machine identifier.
- **Settings show only what applies to your OS.** The External
  terminal picker (Windows Terminal / PowerShell / cmd / WSL) was
  rendered on every platform; it's Windows-only now, with a short
  note on what macOS (Terminal.app) and Linux ($TERMINAL + fallback
  list) do instead. Tray options are hidden on macOS - minimise goes
  to the Dock and the menu-bar icon already offers Show / Quit - and
  Linux gets a caveat about needing StatusNotifier support (stock
  GNOME wants the AppIndicator extension).
- **No more grey strip above the status bar.** The gap under the
  last terminal row rendered in the app background - obvious on the
  light theme. The terminal host now paints in the terminal theme's
  own background and the bottom padding is slimmer (the old extra
  padding compensated for cell-height estimates the fit logic no
  longer makes).
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 8c3538ee43a6211493741253f97d68cddb6374085a82db6b5b081d3049524e1a
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 09f22c27ebdbcebac36f4ccc4a336cc700e3b1394abeb38c1691f07f5de76e1f
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 951f725f88c3e6c819efa73021bc088da725be96334268ade6aba36f24d492ae
windows-arm64 · ssh-tool-windows-arm64.exe 20.4 MB · sha256 5dd96e18a96d4bab637fbb06b00f6cc5979af7ffebef50c71902f5a51e43664a
v0.29.2 · stable
## [0.29.2] - mac + windows field fixes

- **macOS builds stamp their real version.** `task darwin:build`
  produced binaries that showed "dev" in the status bar - the darwin
  build task never injected the git version like linux and windows
  do. Also fixed a macOS-only frontend build failure: two imports
  resolved to the wrong file on a case-insensitive filesystem
  (`confirmModal` vs `ConfirmModal`).
- **Task Manager now says "ssh-tool".** Windows shows the exe's
  FileDescription as the process name, and ours carried the full
  marketing tagline.
- **Off-screen window rescue.** A rare maximise glitch could warp the
  window outside every display - and the new geometry memory then
  faithfully saved that spot and restored it on every relaunch,
  leaving the app unreachable without shenanigans. Saving now refuses
  minimised/bogus coordinates, and restore checks that a grabbable
  piece of the title bar lands on a real display - otherwise the
  window opens centred on the primary screen at its saved size.
- **Last terminal row could render clipped at some font sizes**,
  looking like the status bar overlapped it. The fit logic estimated
  the cell height instead of reading the renderer's real one, which
  rounds to device pixels; at certain font size / DPI combinations
  the estimate was off by enough to keep a clipped bottom row.
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 c23e18f31ce174d4d4a71e0cf8da1e567c216e989b4b189e36281a60a9b9cfce
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 81121d1e15c1b912c5aa7801d547d6d9addde6f16cf29fbc41c32ad88fe230a3
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 991b1f97a9d3343618a56fa0b6c3cc0f84b7b109ddbcae1da93c03de8d5b55b3
windows-arm64 · ssh-tool-windows-arm64.exe 20.4 MB · sha256 c96d98900495e660419d8426d6098956389054583f75b9b899269ac6b26cd69b
v0.29.1 · stable
## [0.29.1] - reopen-last-session fixes

- **Dynamic-inventory and local-shell tabs are now reopened too.** The
  first cut only snapshotted plain SSH tabs, so a mixed set came back
  partial (2 of 3, 1 of 3). Dynamic hosts are remembered by their
  provider-stable external id - the internal row id changes on every
  inventory refresh, which is why Proxmox tabs failed with "dynamic
  entry not found" - and local shells by their shell kind.
- **Quitting right after opening a tab no longer loses it.** The
  snapshot used to lag up to a second behind; it now writes almost
  immediately and flushes once more during window teardown.
- **Slow reconnects are attributed.** A host that takes 30 s to come
  up opens its tab visibly late - it used to look like a ghost
  terminal from nowhere. A "Reopening N tabs from the last session"
  toast now fires when the restore starts, and a "Reopened M of N"
  summary follows if some entries failed. Hosts that left the
  inventory drop out of the snapshot instead of erroring on every
  start.
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 e432b5a22b00d7aca09f69c2af1d15cdfca3c2c85a587cdad5452fe593069068
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 033d0b006c70062f3f94c3b3e762e410961bc3a5b5736cbc6184cc85cd043cf4
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 9aa093867fbdb07096aac50e8a3652455dc7323a2658e98bd8f5539559e8bfe9
windows-arm64 · ssh-tool-windows-arm64.exe 20.4 MB · sha256 45a020a9c2d35e469084dea799ed8e0057d30b49e7f7226feb9f658045d25f24
v0.29.0 · stable
## [0.29.0] - verified updates, palette commands, reopen last session

- **Updates are checksum-verified.** The release manifest has always
  published a sha256 per binary; the app now actually checks it. The
  downloaded update is hashed and compared against the manifest before
  it gets anywhere near the live binary - a mismatch deletes the
  download and aborts the install. After staging, the update modal
  shows "checksum verified" (or a yellow warning if the manifest
  carried no hash, e.g. a custom release server).
- **Update download shows real progress.** Instead of a spinner until
  done, the modal now renders a progress bar with MB and percentage
  while the binary streams in. The Download button shows the asset
  size up front.
- **Update pipeline hardened.** The frontend no longer tells the
  backend what URL to download or which script to run - the backend
  acts only on what its own update check returned. No user-visible
  change, just a smaller attack surface.
- **No more leftover `.old` after a Windows update.** The previous
  binary could linger next to the exe when the old process held its
  file handle a moment too long. The swap script now retries the
  delete, and any survivor is swept on the next launch.
- **Ctrl+K palette runs app commands.** Type `>` (or just search) to
  reach: Open Settings, New local shell tab, Lock vault, Check for
  updates, and Open workspace: X for every saved workspace. Commands
  stay out of the way on an empty query - the `>` prefix narrows the
  list to commands only, VS Code style.
- **Reopen last session's tabs.** On a cold start, if the last session
  had SSH tabs open, the app offers to reconnect them (titles and tab
  groups included) after the vault unlocks - like a browser's "restore
  tabs?" prompt. Settings - Window - Startup switches between Ask
  (default), Always reopen, and Never. Local shells and
  dynamic-inventory tabs are not reopened yet.
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 4ead0227607ae60149a5a94655f97f4535146b66fd2519fac2b4d1b41f7965d7
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 ab1cdf34a2cd8da6db77cd5cb360116e19089ed33d00ea2c602a6be99299b42c
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 9b1d58d782b463be1ea2c09fd0851857fcb6de32e17fae665e7fc65ea06377db
windows-arm64 · ssh-tool-windows-arm64.exe 20.4 MB · sha256 0db83487c6002da7f851cc9d772aba1b58c4570b1d2c1c83f3566bc6e3f8444b
v0.28.0 · stable
## [0.28.0] - window memory, unsaved-changes hint, friendlier errors

- **The window remembers where it was.** On relaunch the app restores
  its previous size, position, monitor (multi-display aware) and
  maximised state instead of always opening at the default size in the
  middle of the primary screen.
- **Save moved into the header with an unsaved-changes state.** The
  connection Save button now lives in the header next to Connect and
  Delete (so it's always visible no matter how tall the form grows -
  e.g. with several jump hops). It's greyed when there's nothing to
  save, tinted yellow with "Save *" when there are unsaved edits, and
  briefly shows "Saved" after saving. A matching hint sits at the
  bottom of the form. Editing a field (a color tag, a username) and
  clicking away is no longer silent.
- **Human-readable error messages.** Errors surfaced from the backend
  used to show the raw JSON envelope
  (`{"message":"...","kind":"RuntimeError"}`). They're now unwrapped to
  the plain message everywhere - port forwards, the credential editor,
  the connection editor and more.
- **Start tunnels and open bookmarks without connecting first.** In the
  Port forwards panel, Start (now "Connect & start") and the proxy
  bookmarks no longer require an open session - they connect for you,
  start the forward, then run, matching what the Ctrl+K palette already
  did. Bookmarks are no longer greyed out when disconnected.
- **Credential editor no longer carries edit mode across entries.**
  Clicking another credential while one was in edit mode left the old
  form open; saving then tried to write the previous credential's name
  onto the new one and failed with a confusing conflict. Selecting a
  different entry now exits edit mode cleanly.
- **"Use different credential" resets between connections.** The
  per-connect credential override panel used to stay open with its
  values staged when you selected a different connection, so it could
  silently apply to the wrong host. It now clears on selection change.
- **Port forwards tidy-up.** The panel moved above Quick actions in the
  connection editor, and the redundant refresh button is gone (live
  status already polls automatically). Bookmark hover is readable on
  the light theme again (was a hardcoded colour that vanished on a pale
  background).
- **Updated Wails to v3 alpha.98** (from alpha.95), picking up upstream
  window-sizing and WebView fixes.
linux-amd64 · ssh-tool-linux-amd64 21.5 MB · sha256 88f2b9de59aab2f4945d5938a60368380fa335e53b09577dabb057fd71dce1af
linux-arm64 · ssh-tool-linux-arm64 20.2 MB · sha256 731eba899b78a3d27bba4f462fde1255ae3a24b62a1e9751f6e1cbebc2461855
windows-amd64 · ssh-tool-windows-amd64.exe 22.0 MB · sha256 7a6d3a0f24f4beb37ed779de8bd32eb4687c37165596d16c286cfc5bc676f3a5
windows-arm64 · ssh-tool-windows-arm64.exe 20.4 MB · sha256 3063fb1cb3600b3c054d2349e675e00a33735faa30d7d78d1c6a4e903653a74c
v0.27.1 · stable
## [0.27.1] - tcpdump decode fixes

- **Row filter now searches decoded content.** The "Filter captured
  rows" box matched only the raw header line, so it missed anything in
  the decode (a CWMP method, a parameter value) and, in verbose mode,
  failed on search terms longer than tcpdump's 16-byte hex-gloss line
  wrap. It now also searches the decoded summary and field values, so
  filtering for e.g. `GetParameterValues` or a parameter path works.
- **No more "CWMP CWMP" rows.** A CWMP continuation segment (the SOAP
  body split across TCP packets, with no method element) used to show
  a meaningless doubled label. It's now labelled `(continuation)` with
  its parameters when it carries any, or declines to a plain HTTP / raw
  decode when it carries nothing useful.
- **Decode-port editor clarifies when it's locked.** The custom
  port-to-proto controls apply at capture start, so they're disabled
  while a capture runs; a short note now says so instead of leaving the
  Add button mysteriously greyed out. (Note: TCP 7547 is auto-detected
  as CWMP, so you don't need an override for the standard port.)
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 3e7909449d5959a194da241a34e3508d6fb8f353cf79a08b5683a7cb4fad0208
linux-arm64 · ssh-tool-linux-arm64 19.9 MB · sha256 30ee37cbe438592f68f1d74caf922936bb2d5707ce398518270af7db276a0295
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 cef5efb0ea8c10362be05aa6579de0c1ef6153caab76f9f15a03231a9b966048
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 11a3dc587728c7fbad13a4a612a5ad964a12825211b75e24a58c680ae507b03c
v0.27.0 · stable
## [0.27.0] - tcpdump: CWMP / TR-069 decode

- **CWMP / TR-069 decoder.** The tcpdump Decode tab now dissects
  CPE WAN Management Protocol traffic (SOAP/XML over HTTP, TCP 7547).
  Each message shows the RPC method (Inform, GetParameterValues and
  its Response, SetParameterValues, Download, Reboot, ...), the Inform
  device identity (manufacturer, OUI, product class, serial) and event
  codes, and the first few ParameterValueStruct Name/Value pairs.
  Falls back to the plain HTTP decode for non-CWMP bodies on the same
  port. `cwmp` (alias `tr069`) is also available as a custom
  port-to-protocol override for ACS setups on non-standard ports.
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 e1096f3db5056a4bc79ddf8e42c3f7647feb1fd26054a857cec6d8f01de58446
linux-arm64 · ssh-tool-linux-arm64 19.9 MB · sha256 0aba6e67d89ff4476771f4e09fbbbcc8d496423b6e46ae8ad1933445e5772f04
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 9bd8db4b025dabcdaece823713d995af6e9b6ac4fe2083aa4946278f8dc1df53
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 0712a464b9fe168cce1ff92e5c4c512d2d0a62280fc51ab3e3e45659a9ce105b
v0.26.4 · stable
## [0.26.4] - cosmetic: ASCII punctuation

- **ASCII punctuation throughout.** Every em-dash, en-dash and Unicode
  minus across the codebase, docs and UI strings is now a plain ASCII
  hyphen. No functional change; purely a consistency / house-style
  pass. (Deliberate UI glyphs like arrows, middots and check marks are
  unchanged.)
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 8a8098b38d4f6aa099fe419250f37889348fcb58fd922c6c6b467274ff0f5ff5
linux-arm64 · ssh-tool-linux-arm64 19.9 MB · sha256 3096afe7dadbbfe1b5b084f1130b0fc91d9449448dfea202c8bd1cbcbc3e0305
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 d605bf9f099ec0435591492b5db38f36e4b2a36f0cc42a6a4b6df93b89f2bb31
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 62225a8f97dee40ef218aa42cd9ac61d9f92e92097bfe06db1612fd1102f103d
v0.26.3 · stable
## [0.26.3] — tcpdump on busy hosts: memory, CPU, bandwidth + window title

The headline is that tcpdump captures on a busy host are now sane.
Previously a continuous capture on a high-traffic interface pushed the
app's memory into the tens of GB within seconds (enough to OOM the
machine), pinned a CPU core, and pulled tens of Mbit/s over the SSH
link. All three are fixed.

- **Memory stays flat.** The live packet stream is batched and
  tail-capped on the backend instead of streamed one event per packet,
  and the live list renders only the recent tail with the heavier
  flows/decode groupings computed only when their tab is open. Memory
  now holds steady regardless of packet rate or capture duration.
  (The v0.26.2 note about this was premature — it bounded one internal
  table but missed the real causes; this is the actual fix.)
- **Far less SSH bandwidth.** Two changes cut the wire volume
  dramatically on a busy capture:
  - The **SSH control connection is now excluded by default.**
    Capturing over the same SSH session is a feedback loop — every
    captured packet is streamed back over SSH, which generates more
    SSH packets that get captured, and so on. The capture now drops
    its own session's traffic automatically (works on any SSH port).
  - A **snapshot length** is applied so tcpdump no longer ships every
    packet's full payload over the link when the view only needs
    headers (and a short payload window in verbose mode for DHCP / DNS
    / TLS SNI).
- **High-rate nudge.** If packets arrive faster than you can read,
  a dismissible banner suggests adding a BPF filter. It does not stop
  the capture.
- **Readable live view.** The flat view is a plain scrollable list,
  newest at the bottom, that auto-follows the tail unless you scroll
  up. The packet count shows in one place (the footer) and no longer
  flickers.
- **Window / taskbar title shows the active connection.** The OS
  window and taskbar entry now read e.g. `myhost - ssh-tool` for the
  active terminal tab, and the section name on the other views.
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 6ef75bff75f3a37040d919a9e9500326b1788cabe10a31cb60078688c92960f3
linux-arm64 · ssh-tool-linux-arm64 19.9 MB · sha256 51f78bf581cbbfde23d7c7b44aea0f1b8d3732b29fbeb8877215a4d1459e8073
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 9494462513aeb104f1fa71822a1c1232cb72ad327408fc2784516f7d71115443
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 0b1a1ff1bb71678b307425ae10b349d634da7ce44f0803d594e22f4b6d4e46ec
v0.26.2 · stable
## [0.26.2] — credential pane polish, tcpdump leak fix

- **Fixed a tcpdump continuous-capture memory leak.** A continuous
  capture on a busy host grew unbounded — the insight analyzer kept
  one tracking entry per distinct connection (4-tuple) forever, so
  short-lived ephemeral-port flows piled up into multiple GB within
  seconds. Idle conversations are now evicted and both internal
  tables are hard-capped, so memory stays flat regardless of how
  long the capture runs or how much traffic it sees.
- **Unmask password fields.** Every password / secret / passphrase
  input in the credential editor (new password, key passphrase, API
  token secret, rotation fields) gains an inline eye toggle so you
  can reveal what you typed before saving.
- **"Used by" entries jump to the tree.** Clicking a folder or
  connection in a credential's *Used by* list switches to the
  Connections view, expands the ancestor folders, selects the row
  and scrolls it into view — no more hunting for where a credential
  is referenced.
- **Multi-select credential drag & drop.** Dragging a credential
  that's part of a multi-selection now moves the whole selection
  into the target folder, not just the grabbed row (matching the
  connections tree).
- **Clearing a credential hint sticks.** Emptying the Hint field and
  saving previously snapped back to the old value; the cleared hint
  now persists.
- **Accurate icon usage counts.** The icon picker's "in use" badge
  now counts credentials too, alongside folders and connections.
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 f1bcc4beee88b914f47be2bac05f869fbad1f97af18c54f77d6c525f3b308cd5
linux-arm64 · ssh-tool-linux-arm64 19.9 MB · sha256 dba44536d05d80997a94c48f07344de6de87f676770c8c5e0ed711e430ff93b6
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 1ec2e052a0dfb617fdf824edb929f18c28e864f9a43ad27c242553b7905097ac
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 d6c94e606a83356f8c3f293249753e641c96559612c3dd17faf60afba5f2b1ac
v0.26.1 · stable
## [0.26.1] — tcpdump follow-ups

- **Find your captures.** The status-bar tcpdump segment now lists
  every active capture in a picker (connection name, which tab it's
  on, interface, packet/insight counts, live/background state) so
  you can jump straight to any of them — not just the focused one.
  A single capture still opens directly on click.
- **Default to `any`.** A new capture pre-selects the `any`
  pseudo-interface so you start by seeing all traffic regardless of
  which device it rides; narrow to a specific NIC once you know.
- **Clearer "tcpdump missing" error.** A host without tcpdump (or
  with it off PATH) now reports "tcpdump not installed on the remote
  host (or not on PATH)" instead of a bare "exited with status 127".
  Covers bash, dash/Debian and the exit code with no stderr.
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 909666f1b33a9caf184e883785645c26779abfb5aec76444c4c3e16ed723d8ad
linux-arm64 · ssh-tool-linux-arm64 19.8 MB · sha256 be17522698dfddd2b2fcf91412a5f9302ecef2a583832d32c8d9f7a7f9f76a3f
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 dbe80a37725c023435e82b637c1b4851b62dc10d3806e76c566be8fb0b542dcd
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 bf6e1cac03abed13c920ff58394677faba6fdf67bf9395eaac123a214be4557f
v0.26.0 · stable
## [0.26.0] — tcpdump network insights, background captures

**Network-health insights.** The tcpdump panel grows an
**Insights** tab (on by default) that runs a passive analyzer over
the live packet stream and flags routing / wrong-interface
problems as they happen:

- **UDP reply from a different source IP** — the classic
  0.0.0.0-bound-service symptom, where the kernel's return route
  egresses a different interface than the request arrived on and
  the client drops the answer.
- **TCP SYN with no reply** (half-open), **ICMP
  unreachable / redirect / TTL-exceeded**, **ARP for an off-subnet
  address**, **repeated TCP resets**.

Each routing-related finding has a **Check route** button that
runs `ip route get <dst> [from <src>]` on the host and shows the
interface, source IP and gateway the kernel would actually use —
the ground truth for "is traffic leaving the wrong interface".
TCP flag-based checks need Verbose on (brief output omits flags);
UDP / ICMP / ARP work in either mode.

**Background captures that survive detach.** A capture is now tied
to its session, not the window or pane that started it:

- Minimise a capture (the `–` button) and it keeps running in the
  background — the modal hides but packets, Insights and counters
  keep flowing. The bottom **status bar** gains a pink activity
  segment with the running packet/insight totals (click to
  restore); the pane's tcpdump icon shows a small green dot.
- Splitting, SFTP-splitting, closing one side of a split, and
  switching tabs no longer interrupt a running capture.
- **Detaching a tab** to its own window keeps the capture alive;
  the new window re-attaches automatically, pulling recent history
  from a backend ring buffer and continuing the live stream.
- A capture's context (interface, BPF filter, verbose / insights /
  continuous, packet count) shows in a status line under the modal
  header and is restored on re-attach, so you never lose track of
  what's running where.

**Continuous mode.** A new toggle runs tcpdump without a packet
cap, for long-lived captures (and the only way to keep one running
long enough to detach). Capped captures that hit their limit now
say so explicitly instead of looking like they crashed.

**Fixes.**
- TCP packets are now classified correctly (tcpdump prints them
  with a `Flags [...]` field and no literal "tcp" token), so the
  half-open and RST-storm detectors actually fire on real
  captures.
- Non-BOOTP traffic that tcpdump mislabels as DHCP (PacketCable /
  DOCSIS provisioning rides UDP port 67 too) is detected and left
  as a plain UDP row instead of rendering a bogus DORA transaction
  with scrambled IPs. The filter keys on the packet header, not
  the port, so genuine DHCP on non-standard ports still decodes.
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 b58e67244b667b270b2c93b588f9effa73c93955ab74b8559856dc866e7e72ed
linux-arm64 · ssh-tool-linux-arm64 19.8 MB · sha256 08bf21e89eea1d6217289b51bd2fc1c23db8e47a966674c16838c794238283c0
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 a2e970b75bf71d6d06a3cb8195f3bdc2213f5f57e22c48810057ad9d8f7152c3
windows-arm64 · ssh-tool-windows-arm64.exe 20.1 MB · sha256 b6ea3841fdcfce650aefbdadf118476fc1e84f5d002edc27cd8ded862fbe3a81
v0.25.0 · stable
## [0.25.0] — Multi-broadcast, v0.16 security follow-ups, quick wins

**Multi-broadcast groups.** The single global broadcast set
becomes a map of named groups. Open the broadcast manager, pick
a group from the dropdown (or hit "+ New group"), drop sessions
into it. A session can sit in zero, one, or several groups;
keystrokes from any member fan out to the union of every group
it belongs to. The legacy default group stays — existing
single-group call sites work unchanged.

Visual cues throughout:
- The peach corner badge on broadcasting panes adds an inline
  pill listing the groups (`⊕ BROADCAST [ops, default]`); hover
  for the full list when truncated.
- Tab labels grow a small peach pill next to the radio glyph
  when the tab's sessions span 2+ groups (`ops,dr`).
- Pane toolbar tooltip names every group the session belongs to.
- Manager dropdown shows the per-group member count; status-bar
  pill shows the unique total across every group.

Manager UX:
- "Show sessions in other groups" toggle (off by default)
  hides candidates already in another group so the picker stays
  focused. Sessions in the active group always show so they can
  be unticked.
- Select all / none / invert operate on what's visible — never
  silently pull in hidden cross-group members.

Groups live in memory on the backend only; persistence across
restarts tracked in TODO.

**Reconnect button on disconnected panes.** A prominent blue
inline pill renders next to the disconnect hint when a pane
loses its session. One click tears down the dead handle and
opens a fresh session bound to the same connection.

**Connect auto-retry on transient failures.** A first connect
attempt that trips DNS / connection-refused / network-unreachable
/ timeout / connection-reset now triggers one silent retry after
800ms before surfacing the error. Auth / handshake / host-key
failures never retry — those are deterministic.

**Vault-locked detected during connect.** When an SSH attempt
fails because the credential's secret couldn't be decrypted
(the vault is locked, not the secret missing), the backend now
emits a typed event the frontend listens for: VaultGate re-pops
and a toast names the connection that was blocked. Previously
the user saw "password missing in vault" with no obvious next
step.

**Jump-chain hover tooltip.** Connection rows in the tree show
`hostname` plus, when a jump host applies through the folder
cascade, `via bastion1 -> bastion2` on hover. No click needed.

**Inherited settings shown inline in the editor.** Username,
port, credential, and jump chain inputs in the connection
editor render an italic "inherited from <folder>: <value>"
hint underneath whenever the field has no override. The
placeholder also picks up the inherited value so the input
visually previews what would be used. Replaces the old
collapsed "Resolved settings" raw-JSON dump at the bottom of
the form.

**Vault Lock in Settings → Vault.** Manual "Lock now" button
in the Vault settings panel, next to the auto-lock minutes
slider. Hits `VaultLock(true)` (forgets the sidecar so the
next launch prompts too) and dispatches the same window event
the auto-lock timer fires, so VaultGate re-prompts immediately.

**Dynamic-folder error retry.** The red `!` dot next to a
broken dynamic folder is now a button — click triggers an
immediate refresh, and the dot clears on a successful pull
(the folder metadata is re-fetched alongside the entry list).
First expand of a folder with a stored `last_error` also pops
a one-time toast so the dot doesn't get missed.

**Dynamic inventory bulk connect.** Ctrl-click selects multiple
dynamic entries (existing); Enter on a selected one now opens
every selected entry in parallel via
`connectionActions.connectDynamicMany` instead of just the
focused row. Partial failures land per-row in the existing
last-connect-error map.

**Filter-aware shift-click range.** Type "vpn" to narrow the
tree, shift-click two visible matches — the range walker
honours the active name / tag filter so only visible rows get
picked up. Previously the walker walked the underlying tree
order and pulled in invisible rows between anchor and target.
Applies to connections, folders, and dynamic entries.

**Layout: tab rows wrap when the window is narrow.** The root
nav row (Connections / Credentials / Settings / Terminal /
Local shell / Search) and the per-tab session bar both grow to
fit however many rows the layout needs. Used to clip at a
fixed grid height; long-named connection clusters now wrap to
a second row instead of disappearing off-screen behind a
scrollbar.

**SQLite "database is locked" fix.** The modernc/sqlite pool
allowed concurrent writers, so an editor save that landed
mid-dynamic-refresh hit SQLITE_BUSY instantly. `busy_timeout`
goes to 5 s and the pool caps writers at 1 (SetMaxOpenConns(1))
so SQLite serialises transactions itself; WAL keeps readers
wide.

**Security follow-ups (v0.16 audit Medium findings):**
- **Vault heap zeroing on Lock.** In-memory mirror switched
  from `map[string]string` to `map[string][]byte`. Lock wipes
  every cached buffer with zeros before delete; Put overwrites
  the previous value on rotation. A crash dump after Lock now
  sees zeros, not plaintext.
- **Vault ciphertext padding.** Plaintext gets length-prefixed
  and zero-padded to 60 / 1020 / 4092 / 16380 bytes before
  AEAD seal (or next 16 KiB multiple for larger secrets). An
  attacker with file-read but no key can no longer fingerprint
  short-vs-long passwords by file size. Legacy entries open
  fine and re-pad on next Put.
- **Pre-auth banner buffered until host key verified.** The
  SSH BannerCallback fires *before* HostKeyCallback returns;
  painting it raw lets an unverified peer drop VT100 sequences
  (cursor jumps, title set, color reset) into the user's
  terminal. Banner now accumulates into a buffer per hop and
  only flushes after `ssh.NewClientConn` succeeds.
- **Pending-restore staged files encrypted.** Between
  `Restore()` and the swap that `ApplyPending()` runs at next
  start, staged `store.db` + `vault.enc` previously sat as
  plaintext under `pending-restore/`. They're now sealed with
  the same passphrase the user proved at restore time; the
  passphrase itself is sealed under the machine+user key so the
  next startup decrypts without re-prompting. A stolen
  pending-restore directory off the machine decrypts nothing.
- **Sidecar v2: real DPAPI on Windows, machine-id strict on
  Linux.** Auto-unlock sidecar no longer derives its key from
  `%COMPUTERNAME%` or hostname. Windows builds wrap with
  `CryptProtectData` (user scope); Linux requires
  `/etc/machine-id` and refuses to write v2 without it (v1
  fallback for containers etc still works). macOS keeps v1
  until Keychain integration lands.

---
linux-amd64 · ssh-tool-linux-amd64 21.0 MB · sha256 f1ea3bb340be3f3cdd983c8e0008312b869ce5b84ad7c366034b7eb79ddc63e5
linux-arm64 · ssh-tool-linux-arm64 19.8 MB · sha256 7a6c43d8e2257660f42ec364c56d3251e9bb87661e602315d18c9b37e05a85c0
windows-amd64 · ssh-tool-windows-amd64.exe 21.5 MB · sha256 42a1551b8ed70b0440ec8deb91c4edbc9082870a6ae83db070198e918318ce6c
windows-arm64 · ssh-tool-windows-arm64.exe 20.0 MB · sha256 9b8e9adf516f039e43e285af1958f7da4b0766a17cea06d70638c874273da900
v0.24.0 · stable
## [0.24.0] — Tcpdump: 8 new decoders, custom port mapping, any-iface

**Eight new protocol decoders** in Verbose mode: HTTP (request +
response, method/path/Host/UA, status code/reason/content-type),
ICMP/ICMPv6 (echo, unreachable, time-exceeded, NDP), SSH banner
(client + server software version), NTP (mode/version/stratum),
SNMP (v1/v2c/v3 + community string + PDU tag), LDAP (op +
messageID), SMB (SMB1/2/3, encrypted SMB3, command name), MQTT
(packet type + PUBLISH topic). Each lands in the Decode tab as a
typed row with its own color in the proto column. DHCP / DNS /
ARP / TLS-SNI from earlier releases are unchanged.

**Custom port → proto mapping.** Top of the tcpdump modal now
has a chip row (Verbose only) where you teach the decoder about
non-standard ports — pick a port, pick a proto, hit Add. Now an
HTTP server on 9000 or an MQTT bridge on 1885 dissects properly
without forking tcpdump. Sent to the backend as
`port_overrides` on TcpdumpStart.

**"any" interface.** The interface dropdown now includes
Linux's `any` pseudo-iface up front. tcpdump supports it but
the kernel doesn't expose it through `/sys/class/net`, so it
never showed. Pre-select still picks a real NIC so a plain
Start doesn't quietly fleet-capture.

**Tcpdump memory bloat fix.** Two parts:
- Frontend: incoming packets now batch through a
  requestAnimationFrame queue, so a 1000 pkt/s burst triggers
  ~60 reactive Svelte updates per second instead of 1000. The
  in-memory cap also drops from 2000 to 800 in Verbose mode
  because each packet's payload + decoded fields runs ~5x
  larger than brief mode.
- Backend: hard cap of 256 lines on the verbose continuation
  buffer so a malformed stream or a giant hex dump can't grow
  payload without bound.

**Flow detail light-theme fix.** The expanded "flow packets"
panel inside the Flows tab had a hardcoded near-black
background (`#0e0e16`) that ignored the theme switch. Routes
through `var(--crust)` now, so light theme stays light.

**Decoder helpers refactored.** `findIPStart` auto-detects L2
vs L3 captures (`tcpdump -X` is L3 most of the time; -e or
some platforms include a 14-byte Ethernet header). The
previous helpers assumed L2 and silently returned -1 on L3
dumps, killing every text-protocol decode. `extractASCIIPayload`
now parses the hex column directly rather than guessing where
tcpdump's ASCII gloss starts.

---
linux-amd64 · ssh-tool-linux-amd64 21.0 MB · sha256 b085fd2c93d0c9b6032a576170887c4d75fe5ece48743d1d3b80766090b78455
linux-arm64 · ssh-tool-linux-arm64 19.7 MB · sha256 dc687909253e9e35cb21543bd1f9441596e9a24495028f48cff6c49046abeb3c
windows-amd64 · ssh-tool-windows-amd64.exe 21.5 MB · sha256 316fbc6b8946893a1445cadfa597792bf3d2b2f88962a45f69ad3a9d93758505
windows-arm64 · ssh-tool-windows-arm64.exe 19.9 MB · sha256 4920b50c9d7e17f203e9685118841df252bae57245737ea7db1cc2e9c0f2d7e3
v0.23.2 · stable
## [0.23.2] — Local shell defaults, archive file picker, UI polish

**Local shell follows Settings.** The top-bar **Local shell**
button used to always hand `""` to the backend, which resolved
to the first available shell per platform (WSL on Windows
whether you wanted it or not). It now follows a new
`local_shell_kind` setting. The button label shows the current
default (e.g. `Local: WSL`, `Local: PowerShell`), the dropdown
chevron got a "Default for plain click" selector, and Settings →
Connection has a matching radio list filtered to the current
platform (no WSL row on Linux).

**Import archive: file picker.** Settings → Import archive
got a **Load file…** button next to Fetch. Pops a native open
dialog and reads the archive's contents into the textarea so
you don't have to copy-paste a 5 MB TOML by hand. 32 MiB limit
keeps a stray binary pick from freezing the renderer. The
chosen path is shown under the textarea for verification.

**Color picker custom hex actually applies.** Custom hex
override didn't reach the parent's editing state when the user
clicked Save before the input blurred (race in some WebView2
builds). Apply now fires on every keystroke as soon as the
value parses as a valid hex. Auto-prepends `#` when missing so
pasted `a9a9a9` works the same as `#a9a9a9`.

**Search button label trimmed.** The `Ctrl+K` pill next to
Search in the top bar looked cramped and was redundant with the
hover tooltip — removed. Search button is now just the icon +
"Search".

**Button heights aligned.** The Connect / Use different
credential / Delete trio in the connection detail header didn't
line up because `.ghost` painted a real 1px border while the
base `button` had `border: 0`. Base now uses
`border: 1px solid transparent` so every variant lands at the
same height, with `line-height: 1.2` for predictable vertical
sizing.

**`Ctrl+S` save-row label removed.** The Save row in the
folder / connection editor had a far-right `Ctrl+S` hint that
sat awkwardly aligned and added noise next to the prominent
Save button. The shortcut still works.

---
linux-amd64 · ssh-tool-linux-amd64 20.9 MB · sha256 1848a27661cc3baee093f0a7427de2786d545229ac73a4d23d17e9d87b167045
linux-arm64 · ssh-tool-linux-arm64 19.7 MB · sha256 c7252c47a140180ce307588f16469038a1bf56479d94c24e299e51caf3e9fd4a
windows-amd64 · ssh-tool-windows-amd64.exe 21.4 MB · sha256 9876f2e4e743767a9c09be07d8f65c1ccb433261a547675aafcae948da697cd3
windows-arm64 · ssh-tool-windows-arm64.exe 19.9 MB · sha256 65df52e6d4f05d469ccc2b748774d8b9df6d422b987449e0538a6d3ca5e5dbdf
v0.23.1 · stable
## [0.23.1] — In-app dialogs everywhere, tree shift-select fixes

**Native dialogs replaced.** Every remaining `window.confirm` and
`window.alert` call site now routes through the in-app
`PromptModal` / `ConfirmModal` / toast helpers introduced in
0.23.0. The ugly `wails.localhost says` chrome won't appear from
credential / port-forward / SFTP / palette / settings / update /
dynamic-entry flows anymore. Error popups become non-blocking
toasts; destructive confirms render with a red action button at
z-index 2000 above every editor overlay.

**Palette → terminal focus.** Closing the command palette
(Ctrl+K) or the snippet palette (Ctrl+Shift+P) with Escape now
returns focus to the active terminal so you can keep typing
without an extra click. Focus is only stolen when the terminal
view is current; opening a palette from Settings / Connections
still leaves focus where it was.

**Folder shift-click selects ranges.** Holding Shift while
clicking a folder row in the connections tree now selects the
range between the current anchor folder and the clicked target,
matching the existing behaviour for connections. Ctrl/Cmd-click
still toggles individual folders. Same range support was added
to credentials so you can multi-select credentials across folder
boundaries for batch delete.

**Cross-folder connection range fixed.** Shift+clicking a
connection that lived under an expanded grandchild folder was
selecting the wrong span (or nothing at all) because the flat
index used by the range walker didn't match the on-screen order.
The flat index now descends into subfolders first, then emits
sibling connections — matches what the user sees.

**Export archive: orphan parent_id fix.** Exporting a subtree
(quick-share or Settings panel) or a hand-picked set of
connections used to write `parent_id` / `folder_id` references
that didn't exist inside the archive itself. The importer's
defensive "place at root" fallback kicked in, dropped every
top-level folder under root, and emitted a long warnings list.
The exporter now strips those references at write time so the
import lands cleanly.

---
linux-amd64 · ssh-tool-linux-amd64 20.9 MB · sha256 fe1e01ddda13f3ec7efc004ddc669a2cc4e2a765ac34403aabc8a142f489fe8b
linux-arm64 · ssh-tool-linux-arm64 19.7 MB · sha256 84b221a2d8b0b1fa18c1ccea81e14aa5afb073f560d6251276d47d1fd901c2ce
windows-amd64 · ssh-tool-windows-amd64.exe 21.4 MB · sha256 3b0b6f233300e2708595397b627916706fe6f825c4d20886c80cecbfe58777d6
windows-arm64 · ssh-tool-windows-arm64.exe 19.9 MB · sha256 90cd01163a8a51c5f4143c9563b833049b0ce894dda3ee1fab4c781cdb05ecf9
v0.23.0 · stable
## [0.23.0] — Dynamic-to-static conversion

**Pin dynamic host as connection.** Single-entry detail pane gets
a **Pin as connection…** button that promotes a dynamic
inventory host into a permanent connection inside the same
folder. The new row carries any lifted Ansible vars
(`ansible_user`, jump chain) into its overrides; the per-attempt
credential override (if set) becomes the connection's
default credential. The pin is recorded in a new
`pinned_dynamic_entries` table — future inventory refreshes
skip the original `external_id` so the host doesn't appear
twice (once as the real connection, once as a dynamic ghost).
Deleting the pinned connection drops the pin via FK cascade and
the next refresh re-includes the host as a dynamic entry, so the
operation is reversible without a dedicated Unpin button.

**Convert dynamic folder to static.** Inside the dynamic folder
editor, a new **Convert to static…** action snapshots every
current host into a regular connection in the same folder, then
strips the provider link entirely (drops the `dynamic_folders`
row, clears cached entries, stops the refresh timer). The base
folder stays in place so inheritance keeps working for the
freshly-created connections. Useful when you're handed a one-off
Ansible inventory that won't change again, or when you want to
freeze a cloud provider's snapshot before retiring the API
token. Irreversible from the UI; existing pinned connections are
left untouched.

**Schema.** Migration `v15` adds `pinned_dynamic_entries`.

---
linux-amd64 · ssh-tool-linux-amd64 20.9 MB · sha256 773dd081ed61ec3c4ec4bafae4078a51d1271ae5b75eba8cbfc70a453a2ebb5f
linux-arm64 · ssh-tool-linux-arm64 19.7 MB · sha256 cdab8f4bd4099e4135138fa024297f9b0aa0f7ab1ecfda38962295446777c20f
windows-amd64 · ssh-tool-windows-amd64.exe 21.4 MB · sha256 a57fcf062a6dd7f2aa8755e1d5b3e5b52b5a6e9bcb606b309e32af0ec3090da4
windows-arm64 · ssh-tool-windows-arm64.exe 19.9 MB · sha256 012af63146a042314070f1cc515d42bc4ff6883b54267bc91acbb3d4a7575c55
v0.22.0 · stable
## [0.22.0] — Ansible inventory, password history, security hardening, UX polish

**Ansible static-inventory provider.** New dynamic-folder provider
reads a local `.ini` or `.yml` inventory file (extension picks
the parser) and surfaces every host as a flat dynamic entry,
with every group it belongs to as a tag. Per-host
`ansible_user` / `ansible_port` / `ansible_host` lift into SSH
overrides at connect time. Jump host parsed out of
`ansible_ssh_common_args` / `_extra_args` (`-J`, `ProxyJump=`,
and `ProxyCommand=ssh -W` shapes recognised). Dedicated
**Jump host credential** picker on the folder config (target
host creds rarely authenticate on the bastion) and per-connect
override for the jump host + jump credential. Native file
picker for the inventory path. New entry kind `server` (icon:
Server tower) so Ansible hosts don't carry the misleading "VM"
badge — consistent VM / LXC / Server icons across the tree,
Ctrl+K palette, and detail-pane header.

**Password / API-token history.** Every successful rotation now
snapshots the previous secret into a sealed vault entry (same
master key as the live credential) and records a row in the new
`credential_secret_history` table. The credential detail panel
gets a collapsed **Previous secrets** section listing the last 5
rotations newest-first: per-row Reveal / Copy buttons with the
same 30-second auto-clear as live reveals, and a × to forget a
single snapshot. Retention is hard-coded at 5 for now (slider
follow-up later). On credential delete every history vault
entry is purged alongside the live one. Schema bumped to v14.

**Quick palette polish.** Label always shows the entry's
canonical name (folder path + name) — typing a tag no longer
makes every matching row read as that tag. Tag chips render
in the sub-line with the matching one highlighted yellow so
the user still sees *why* a row matched. Dynamic entries are
indexed by tags too; auto-connect chain on bookmark click
opens the SSH session, starts the tunnel, and launches the
browser in one shot.

**Sidebar tag filter** narrows down by tag substring across
both regular connections and dynamic entries. `TreeNode`
respects the same match when expanding a dynamic folder so
only matching hosts show under the bucket.

**Security follow-ups (from the v0.16.0 audit).**

- **SSH agent socket path now validated** before we dial it. Any
  UNIX socket on the same machine would previously be dialled as
  if it were the user's agent and asked to sign challenges. We
  now require the path resolves to a real socket inode (not a
  symlink), is owned by the current user, and its parent
  directory is owned by the current user with permissions no
  looser than 0700. No-op on Windows where the agent is a named
  pipe with OS-managed ACLs.
- **Host-key challenge has a 2-minute timeout.** If the user
  closed the modal without responding (frontend bug, hard
  crash, or a deliberate hang), the connect goroutine sat on
  the response channel until app shutdown, holding a half-open
  SSH handshake. The callback now `select`s on the channel,
  context cancel, AND a 2-minute fallback that logs and
  rejects.
- **opkssh provider YAML is validated before the OIDC flow
  fires.** Hostile YAML pasted into the credential's config
  editor used to be honoured as-is: an attacker-controlled
  `issuer` could redirect the user's identity to a hostile
  IdP, and a non-loopback `redirect_uris` entry would
  exfiltrate the auth code (and any access_token) to an
  attacker host. We now require the issuer is `https://`
  (loopback `http://` allowed for OIDC dev work), every
  redirect_uri is loopback (`localhost`, `127.0.0.0/8`,
  `::1`), and the same checks run against the optional
  `remote_redirect_uri`. Per-rule unit coverage in
  `opkssh_validate_test.go`.

**Quality-of-life polish.**

- **Toast notification system.** Bottom-right corner host renders
  short non-blocking confirmations (`saved`, `failed: …`, etc.).
  DetailPane Ctrl+S, credential save / rename / change-type /
  API-token rotation / opkssh-config save / credential create
  now all fire a toast — visible regardless of where the Save
  button has scrolled.
- **About panel shows the live schema version** instead of a
  hard-coded "migration 10". Backend resolves `SchemaVersion()`
  from the store and ships it in `AppVersion()`; future
  migrations no longer require a doc update.
- **Status-bar version pill jumps straight to About.** Previously
  it opened Settings on the user's last-active section. The
  Workspaces "Manage…" link uses the same new
  `view.setTabSettingsSection()` helper.
- **Documentation refresh.** USER_GUIDE picks up the new
  shortcuts, tunnels-from-terminal flow, status-bar tunnels +
  toasts, and the auto-connect bookmark behaviour. `gotchas.md`
  archives the recent Windows-update CMD-window, xterm
  Ctrl+Tab, tab-focus-restore, palette-stopPropagation, and
  detach/redock-pane-tree traps.

---
linux-amd64 · ssh-tool-linux-amd64 20.9 MB · sha256 85d68bac9f1fd6af96355617613d48a8f4386329f25a949984f4fca86cf695dc
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 5ac6c31ba9ee738e07391fb3a6b40ed5ad976e7dcafdac59e24948ebeb4fa48c
windows-amd64 · ssh-tool-windows-amd64.exe 21.4 MB · sha256 9b0c852a9c5149b5a83584e4c0edd3555b56907571bd7063c9effe9e3042aa9d
windows-arm64 · ssh-tool-windows-arm64.exe 19.9 MB · sha256 860b490de1b267ab3467d91c69d7bcf141288c2c6a0fb3242ba930b9680d4f6a
v0.21.0 · stable
## [0.21.0] — tunnels & bookmarks from terminal view

**Per-pane tunnel popover.** The SSH-only toolbar group on every
pane header gains a cable-icon button that opens a compact
popover listing every port-forward configured on the pane's
connection. Each row has a Start/Stop toggle that operates on
the pane's own session; dynamic (SOCKS5) forwards expand to show
their bookmarks, and each bookmark is one click away from
opening in the isolated browser (auto-starting the SOCKS5
tunnel first if it's down). The button shows a small green
badge with the running-tunnel count when at least one forward
on this session is up, otherwise no badge. Disabled when the
session isn't connected — the popover stays read-only rather
than auto-reconnecting.

**Quick palette indexes tunnels and bookmarks.** `Ctrl+K` now
matches forward descriptions and bookmark names alongside
connections and folders. A matched forward row shows
`↵ start` / `↵ stop` depending on its current state; a
bookmark row shows `↵ open` and routes through the SOCKS5
listener (auto-starts it first if needed). Forwards and
bookmarks are excluded from the empty-query view and given a
small ranking penalty so a query that matches both a host and a
tunnel description surfaces the host first. Search hits across
description, parent connection name, and "tunnel" /
"bookmark" / "browser" synonyms.

Backend gains `ForwardsListAll()` so the palette can build its
index in one IPC instead of one-per-connection.

### Bug fixes carried in this release

- Auto-update on Windows left a stray CMD window open after the
  app restarted. `DETACHED_PROCESS` only severs the parent's
  console; when the helper executes `cmd.exe` (a console-subsystem
  binary), cmd allocates its own console regardless. Swapped to
  `CREATE_NO_WINDOW | CREATE_NEW_PROCESS_GROUP` so the helper
  runs invisibly and the window-less behaviour is the actual
  flag, not a side effect.
- `Ctrl+Tab` / `Ctrl+Shift+Tab` / `Ctrl+1..9` did not fire when
  the terminal had focus: xterm's textarea swallowed the
  keystroke before it bubbled to the window-level listener.
  Terminal's custom-key handler now refuses those combos so the
  global shortcut handler in App.svelte gets them.
- Tab switching (via shortcut, `Ctrl+1..9`, or tab-bar click) no
  longer leaves focus on the previous tab's xterm. A shared
  `focusActiveTerminal` helper restores focus to the active
  pane's xterm after two animation frames, gated on the
  `.tab-content.active` host so the right tab's terminal wins.
  The snippet palette migrated to the same helper for
  consistency.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 d0eb274b63ca517ce04498a8828e5b27fa97c93cdca8f90e95cf536dc67201a8
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 46031805b69165d1313d0a97c28b2cdcd8023375eee87e483bef1f5a4b22585a
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 dafd9d7779b99ea5700093441eed7d11b90dfbca43812994f4ac571cb0f57fed
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 8342a47838aedddd12f914866b631169c5c69ef183e16240364551da0dc55629
v0.20.0 · stable
## [0.20.0] — tab navigation shortcuts + detach/redock keeps splits

**Keyboard shortcuts for tab navigation.** Five new global combos
on the terminal view:

- `Ctrl+Tab` / `Ctrl+Shift+Tab` — cycle to the next / previous
  tab with wrap-around.
- `Ctrl+1` … `Ctrl+8` — jump straight to tab N.
- `Ctrl+9` — jump to the last tab (Chrome / VS Code parity).
- `Ctrl+Shift+W` — close the active terminal tab. The Shifted
  variant is used so we don't fight readline's `delete-word` in
  the embedded shell.
- `Ctrl+Shift+T` — reopen the most recently closed tab. SSH
  connections are reopened via `sshConnect`; local-shell tabs
  aren't reopenable (their identity dies with the PID) and are
  skipped at close time. The undo stack is capped at 32 entries.

All five only fire when the terminal view is showing so they
don't hijack `Ctrl+1` on the sidebar or steal focus inside
Settings forms.

**Detaching a tab with split panes no longer flattens them into
separate tabs.** The detach IPC used to ship only the
comma-separated session IDs, so the detached window rebuilt the
tab by calling `addTab()` once per session — every split surfaced
as its own bare tab in the new window, and the same flattening
happened on the way back through redock. Detach + redock now
serialize the full `PaneTab` (title, pane tree, group name +
colour) as a URL-safe base64 JSON blob carried alongside the
session list. Both the detached window's bootstrap and the main
window's `window_redock` listener prefer that blob and fall back
to the old per-session reconstruction only when it's missing.
Internal pane / split / tab ids are regenerated on restore so the
moved tab can never collide with ids already in the destination
window.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 e1968849a97fdce808e8bf0f06289c18294787d677310094aaf5d21425c934f3
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 8ae4d06ace12771db039e25c67c58a63e325c088558fb697aad8ae21f290176b
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 2566a7cc221adeaa3acd0304e2997a04f2fa8302aba9ef7c75d66a786460d650
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 7c8a6375b112f939ea5a7022270e2fbff3103651d285128a9bf727d800af87b2
v0.19.4 · stable
## [0.19.4] — snippet focus + Check now modal

**Snippet palette returns focus to the active terminal.** After a
snippet fires, the palette closes and the view flips to the
terminal — but focus stayed on `<body>`, so the user had to click
into the pane before they could keep typing. The palette now
restores focus to the active `.xterm-helper-textarea` after two
`requestAnimationFrame` hops (waits out the modal teardown and
the terminal view's `display: none → flex` flip).

**Settings → Updates → "Check now" opens the release-notes modal.**
The page no longer pops a Download + Apply pair of buttons next
to the Check button. Instead a successful check with a newer
release available opens the same modal the status-bar pill uses,
giving the user the release notes, Download, and Restart and
install flow in one place. When no update is available a short
"You're on the latest version" line shows inline.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 d0d952c2b99fdbbbba46cf635d0a69ccaf4fe0c0e95168ed08581cb91047c13f
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 f87e072766e836ad7de74d69e2ae0af5bc6d502143b2039e53b737d15ff9a9c4
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 1b50052acf549ad4b3e0ffe8d364da8e4c93aac7ecfeb8071f170be4fee3fc38
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 cc353741d663f5c9f3a2ea8eae798782d9cc9fb79d2aa69b1e23bbca7c05a0a4
v0.19.3 · stable
## [0.19.3] — audit log: wider table, sort, expand, substring filter

### Audit log

The Settings → Audit log table was cramped, truncated metadata
strings, and the action filter only accepted an exact match
(typing `vault` returned 0 hits because the events are
`vault.unlock`, `vault.rotate`, etc).

Changes:

- The section drops the 780 px width cap and the table expands to
  fill the window.
- Host / port, user, and a target name are extracted into their
  own columns so the row tells you "what host, which user" at a
  glance.
- Long values wrap inside their cells (`word-break: break-word`)
  instead of overflowing the container.
- Time and Action headers are sortable — click to flip direction.
- A per-row "expand" toggle reveals the full key=value blob in a
  grid; collapsed view shows the first two extra fields plus a
  `+N` chip.
- Filter is now client-side over the loaded page (no DB round-
  trip per keystroke) and searches the action, target, **and**
  every metadata value. `vault` matches every `vault.*` event,
  `ssh` matches both `ssh.connect` and `ssh.disconnect`, an IP
  like `10.0.1.5` matches every row that touched that host, a
  username like `root` matches by user. The label changes from
  "Filter action" to "Filter" to reflect the broader match.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 7bb882eb435898145cf2162c1a09f6913b8577aa180a32e6c1aa3487b5a9b22d
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 aeb873d2d4c677f81d4966f1daeafbc35d9eb85555a55a158f87aa8c919e119c
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 534d0fea31b6c0c78dffa7de7a99ce93b7351bb3bdedcd863f1d0cea3be14e9f
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 8b021ae751fe8487755e921049c00fda02e141053dabf50f98de3665838b40b1
v0.19.2 · stable
## [0.19.2] — auto-update fixes

### Updates

**Status-bar "vX.Y.Z available" pill is now end-to-end.** Clicking
it opens the release-notes modal, which now downloads the new
binary and triggers the swap helper in-app, matching what
Settings → Updates already did. Previously the modal's Download
button only opened the system browser, leaving the user to
manually replace the exe.

**"Batch file cannot be found" on Windows install.** The apply
helper was spawned via `cmd /c start "" /b script.cmd` which
resolves the script path relative to whatever cwd cmd inherited
(often the user's Desktop, never the app's data directory). The
helper is now invoked with an absolute path under
`cmd.exe /c <abs>` with the working directory anchored to the
script's own folder. The relaunch inside the helper also pins
its working directory via `start "ssh-tool" /d "<exeDir>" %TARGET%`.

### Known issue

Unsigned binaries trip Microsoft Defender's generic ML detector
(`Wacapew.A!ml`) on a fraction of Windows installs. Code-signing
the release is on the roadmap; in the meantime download links
are SHA-256 stamped and the release page lists the expected hash
so users can verify out-of-band.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 52d2eb27c097f260d246f1f76616ca32f6098d9be2b05d3b597a483a55e19d72
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 3f7241dc6fd47f2902f9364eba8b7fc5f26a6a0f2bf649de93f82ed2a4158d1e
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 9faec2fe3643882fae76b23aa21d232842e9d76154b3c5a65ef6f6190d282b48
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 c9a42b0face1bdedf038f93580f25c4073d7a23585a7a2df9c5f7321c243abd5
v0.19.1 · stable
## [0.19.1] — richer audit metadata, dynamic folder shortcut, provider sort

### Audit log

`ssh.connect` and `ssh.connect.dynamic` now record `host`, `port`,
and `user` alongside the existing `session_id`. The dynamic flavour
also carries the entry's display `name`. `ssh.disconnect` reads
the session's metadata cache before the entry is freed so the log
line gets `host` + `name` too. Previously you could see "something
connected" but not what host or user — now the audit log answers
the only question that matters during an incident review.

### UI

- New "Add dynamic folder" button in the Connections sidebar header
  (globe icon) next to "New folder" / "New connection". Two
  alternative entry points (empty-area context menu, per-folder
  context menu) stay as they were.
- Dynamic provider picker is now sorted alphabetically: AWS EC2,
  DigitalOcean, Hetzner Cloud, Linode, Proxmox VE, Scaleway, Vultr.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 39a299d31a33407fdff14d2c642536bbbb49d9d7bde008a1004194e129eda816
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 efc6e3b56537596470adae8738a80b46fab8d26c1c8a1093f122945175a4c298
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 78dee8c460d9a69a3d93b3069ef14aeac0d63962d740d6f3c89556fd47fb7236
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 02fe20b94387bf1584877cf4348dee3f53854401d96d1beb93b28f1e490fe6c7
v0.19.0 · stable
## [0.19.0] — passphrase rotation, audit log, encrypted pre-restore, auto-update

### Security

**Master passphrase rotation.** Settings → Vault now has a "Change
master passphrase" form. Re-derives the file key from a fresh
Argon2id salt, re-encrypts every entry under the new key, and
atomically swaps the on-disk file. The vault must be unlocked
first; the old passphrase is independently re-verified against the
file on disk before any mutation so a stale in-memory key can't
silently destroy data. If an auto-unlock sidecar exists it's
refreshed with the new passphrase so subsequent launches keep
working. Existing backups still need the OLD passphrase to restore
— make a fresh backup right after rotating.

**Encrypted pre-restore safety snapshot.** `backup.Restore`
previously wrote a plaintext `store.db` + `vault.enc` into
`backups/pre-restore-<ts>/` so the user could roll back a bad
restore. That left two unencrypted copies of every credential
sitting under 0700 dir perms only. The safety snapshot is now
sealed with the same envelope format as a regular backup, keyed
with the live vault passphrase the user just typed. Recovery is
now identical to restoring any other backup file.

**Local audit log.** New SQLite table `audit_events` records
sensitive operations: vault unlock / lock / init / rotate, backup
create / restore, SSH connect / disconnect, update download /
apply. Settings → Audit log shows the live tail with filter +
CSV export + retention purge. The data never leaves the machine
— useful for "did I really connect to that host yesterday" and
for handing evidence to a compliance review without integrating
a SIEM yet.

### Updates

**One-click install on Windows.** Settings → Updates has a
"Restart and install" button alongside the existing check.
Downloads the new binary into the app's own directory, writes a
small `.cmd` helper that waits for the running exe to release its
file lock (poll-rename, up to 60 s), swaps the binary, and
relaunches. The app quits cleanly so the helper isn't fighting
the OS. On Linux / macOS the rename happens during the download
itself; you just relaunch yourself.

### Tests

Two new test files: `internal/creds/rotate_test.go` (round-trip,
empty-passphrase guard) and `internal/backup/backup_test.go`
(round-trip, wrong-passphrase, AAD tamper rejection, pre-restore
snapshot is encrypted not plaintext SQLite).

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 be0b453ac20adb54ee303c6fac0a20ead41ba1fe42c429b4a9b667f72228b91b
linux-arm64 · ssh-tool-linux-arm64 19.6 MB · sha256 df8c7b05b282517118b0d47744fa44c27d7f640c0be5f471d588d5a6ef611d00
windows-amd64 · ssh-tool-windows-amd64.exe 21.3 MB · sha256 3e60f707c4f9bd802658bfd907ccb1dbc113751cecace7e3a37ada4c783c4f9b
windows-arm64 · ssh-tool-windows-arm64.exe 19.8 MB · sha256 3258427d7e9c6c07ca69bd1342a180326d671368ba83441f633d384c2ab19a5e
v0.18.1 · stable
## [0.18.1] — themes, override block, dynamic multi-select, snippet fixes

### UI

**Three themes, full palette swap.** Settings → Appearance → UI
theme picks one of:

- **Mocha** (default) — Catppuccin Mocha. Same dark theme as
  before, now routed through CSS variables.
- **Latte** — Catppuccin Latte. Light background, dark text. For
  bright rooms / projector demos / anyone tired of dark mode.
- **High contrast** — Mocha with muted text steps and borders
  pushed up for direct-sun readability outdoors.

77 Svelte/TS files were swept to replace hard-coded mocha hex
values with `var(--token)` so the palette swaps cleanly. Two
exceptions stay raw on purpose: `lib/themes.ts` (xterm terminal
colour schemes, picked separately) and `lib/ColorPicker.svelte`
(connection / folder colour-tag preset swatches).

**Checked toggles get a coloured border.** `.toggle:has(input:
checked)` adds an accent border + 6% accent-tint fill so the
state is obvious. Settings radio cards got the same treatment so
checkboxes and radios look consistent across the page.

### Connect flow



**Inline connect error replaces the `wails.localhost says` alert.**
The dynamic-inventory detail pane now surfaces failed connects as a
styled inline error row (same look the regular DetailPane already
had), with a "Show raw error" disclosure and a Clear button. The
WebView2 native alert dialog is gone.

**Per-attempt credential override.** New "Use different credential…"
button in both the regular and dynamic-inventory detail panes.
Picks a credential from the dropdown, the next Connect press uses
it for the target hop only, then resets. Nothing is persisted; the
connection's saved auth_ref is left alone. Jump-hosts in the chain
keep their inherited credentials so a temp override doesn't leak
into a bastion.

**Multi-select on dynamic inventory.** Ctrl-click adds / removes,
Shift-click selects a range across the entries in a single dynamic
folder. The detail pane switches to a bulk view with two actions:
Connect all (opens N terminal tabs in parallel) and Batch exec
(runs one command across the selection, results modal). Batch exec
on the backend now accepts `dyn:<entryId>` synthetic ids and
resolves them through the same folder-inheritance path as
SshConnectDynamic.

**Snippet palette: shortcut, broadcast, navigation.** Ctrl+Shift+P
now actually opens the palette when focus is in a terminal (xterm's
key handler was swallowing the keystroke); the arrow-key navigation
inside the modal stopped working because of an overzealous
`stopPropagation` on the modal-keydown handler, fixed by routing
the navigation events through the local handler before stopping
propagation. Snippet send is now broadcast-aware on the backend —
firing into a session that's in an active broadcast group fans the
snippet body out to every member (SSH or local PTY), matching the
keystroke fan-out behaviour. The palette also switches to the
Terminal view after send so the user sees the result without
having to click the tab themselves.

**Keyboard-shortcut help in the tab toolbar.** New `?` button next
to the broadcast pill opens a modal listing every shortcut and
mouse gesture. Cleaner than burying them in docs. — CI/CD release pipeline + broadcast / terminal fixes

**CI/CD pipeline.** Releases now build through GitLab CI: one
amd64 runner (self-hosted Debian) and one arm64 runner (Oracle
free-tier Ampere) build their respective Linux binaries
natively, while Windows amd64/arm64 cross-compile from the amd64
runner with CGO disabled. A custom Ubuntu 24.04-based image
hosts the Wails + Go + Node toolchain, built per-arch and
combined into a multi-arch manifest. Pre-release tags
(`v0.18.0-test`, `v0.18.0-rc1`) upload artefacts to the
project's GitLab Generic Package Registry for testing without
touching the public release server. Plain semver tags publish
to sshtool.app as before.

Setup notes for runners live in `docs/RUNNER_SETUP.md`.

**Tab-bar reorder.** Drag a tab onto another tab's label to
re-insert it at that position; cursor X relative to the target
tab's midpoint decides whether the source lands before or after.
A 2px accent bar shows the insertion side while you hover.

**Pane drop zones.** Dragging a tab (or sidebar connection)
over a pane shows left / right / top / bottom drop zones that
split the pane in the chosen direction. The earlier experiment
with a "centre = swap" zone is gone — replacing the session in
place was too easy to trigger accidentally and left users
without a way to recover the old pane.

**Drag-and-drop merge reuses the source session.** Dragging tab
A onto tab B used to do a fresh `ssh connect` for the merged
pane, which threw away whatever was running on A — open htop,
vim, a half-typed pipeline. The DnD path now moves the source
tab's active session into the target split (no disconnect, no
reconnect); side effect: broadcast membership and any session
state survive the gesture for free, because the sessionId is
the same row.

If the source tab had multiple panes (its own split), only the
active pane moves and the others' sessions stay in the pool
without a UI surface. That's a known limitation; if it turns
out to matter the merge can grow to preserve the full split
tree later.

**Local PTY allowed in broadcast.** WSL / local-shell sessions
can now be members of a broadcast group alongside SSH sessions.
The picker modal lists them, and the backend fan-out writes to
both pools. Useful for sweeps like "type `uptime` on three
remote boxes plus my local journal at once".

**Terminal last-row clipping.** Three layered fixes for the
bottom-row clip seen on htop / less at certain window heights:
two-pass fit() with `requestAnimationFrame` in between so the
second pass measures against committed cell geometry; 50ms
debounce on the ResizeObserver so a drag storm collapses to one
resize per pause; explicit row-count probe after fit that sheds
one row when the host has less than ~60% of a cell-height of
remainder (the descenders on the proposed bottom row would have
clipped). The terminal host also gained `overflow: hidden` and
the xterm wrapper got a `max-height: 100%` cap so the canvas
can't bleed past its container while shrinking.

**Settings checkboxes use the accent colour.** Native WebKit /
WebView2 default checkbox rendering on dark themes drew a
near-invisible grey tick on a near-black box. `accent-color` is
now pinned to the accent blue so checked toggles read at a
glance.

**Batch exec results expand by default.** The results modal used
to leave clean-exit hosts collapsed, which forced an N-click
expand pass to see what the command actually printed across the
fleet. New "Expand all" toggle, default on; per-host details can
still be collapsed individually.

---
linux-amd64 · ssh-tool-linux-amd64 20.8 MB · sha256 8df82dd80e8dc7ec639c27ef280d32206a71aacc7ef721e4d2a16030b0ed153b
linux-arm64 · ssh-tool-linux-arm64 19.5 MB · sha256 db21da8c02852c7a9a5f0bac2f4641a26c7ce815c3e7275203aacbd3c10dfd94
windows-amd64 · ssh-tool-windows-amd64.exe 21.2 MB · sha256 9b751e1f2be4a366fc6efbf4ea856395e94a8f4681bad9d515231da95ca30b0c
windows-arm64 · ssh-tool-windows-arm64.exe 19.7 MB · sha256 e76bf8b5fcc685f260816f278459b9e19fe9e8d0c47734c1d1d72481a76832cb
v0.18.0 · stable
## [0.18.0] — CI/CD release pipeline + broadcast / terminal fixes

**CI/CD pipeline.** Releases now build through GitLab CI: one
amd64 runner (self-hosted Debian) and one arm64 runner (Oracle
free-tier Ampere) build their respective Linux binaries
natively, while Windows amd64/arm64 cross-compile from the amd64
runner with CGO disabled. A custom Ubuntu 24.04-based image
hosts the Wails + Go + Node toolchain, built per-arch and
combined into a multi-arch manifest. Pre-release tags
(`v0.18.0-test`, `v0.18.0-rc1`) upload artefacts to the
project's GitLab Generic Package Registry for testing without
touching the public release server. Plain semver tags publish
to sshtool.app as before.

Setup notes for runners live in `docs/RUNNER_SETUP.md`.

**Tab-bar reorder.** Drag a tab onto another tab's label to
re-insert it at that position; cursor X relative to the target
tab's midpoint decides whether the source lands before or after.
A 2px accent bar shows the insertion side while you hover.

**Pane drop zones.** Dragging a tab (or sidebar connection)
over a pane shows left / right / top / bottom drop zones that
split the pane in the chosen direction. The earlier experiment
with a "centre = swap" zone is gone — replacing the session in
place was too easy to trigger accidentally and left users
without a way to recover the old pane.

**Drag-and-drop merge reuses the source session.** Dragging tab
A onto tab B used to do a fresh `ssh connect` for the merged
pane, which threw away whatever was running on A — open htop,
vim, a half-typed pipeline. The DnD path now moves the source
tab's active session into the target split (no disconnect, no
reconnect); side effect: broadcast membership and any session
state survive the gesture for free, because the sessionId is
the same row.

If the source tab had multiple panes (its own split), only the
active pane moves and the others' sessions stay in the pool
without a UI surface. That's a known limitation; if it turns
out to matter the merge can grow to preserve the full split
tree later.

**Local PTY allowed in broadcast.** WSL / local-shell sessions
can now be members of a broadcast group alongside SSH sessions.
The picker modal lists them, and the backend fan-out writes to
both pools. Useful for sweeps like "type `uptime` on three
remote boxes plus my local journal at once".

**Terminal last-row clipping.** Three layered fixes for the
bottom-row clip seen on htop / less at certain window heights:
two-pass fit() with `requestAnimationFrame` in between so the
second pass measures against committed cell geometry; 50ms
debounce on the ResizeObserver so a drag storm collapses to one
resize per pause; explicit row-count probe after fit that sheds
one row when the host has less than ~60% of a cell-height of
remainder (the descenders on the proposed bottom row would have
clipped). The terminal host also gained `overflow: hidden` and
the xterm wrapper got a `max-height: 100%` cap so the canvas
can't bleed past its container while shrinking.

**Settings checkboxes use the accent colour.** Native WebKit /
WebView2 default checkbox rendering on dark themes drew a
near-invisible grey tick on a near-black box. `accent-color` is
now pinned to the accent blue so checked toggles read at a
glance.

**Batch exec results expand by default.** The results modal used
to leave clean-exit hosts collapsed, which forced an N-click
expand pass to see what the command actually printed across the
fleet. New "Expand all" toggle, default on; per-host details can
still be collapsed individually.

---
linux-amd64 · ssh-tool-linux-amd64 20.7 MB · sha256 ddb6bff8434ca25494a227c73199b6139c990ed47a3ee6187552aad1cc9c4bb7
linux-arm64 · ssh-tool-linux-arm64 19.5 MB · sha256 b079b5463936f268fea67d3a6e8263871758bdda05b9e6563ea2bad77242792a
windows-amd64 · ssh-tool-windows-amd64.exe 21.2 MB · sha256 7307f1daef9fd24ed2fb33db873a5772061f03d3d101de31277f0cda70928957
windows-arm64 · ssh-tool-windows-arm64.exe 19.7 MB · sha256 701449f276154a97ee3716380b1cde8eedcdfb6801b6510df17dc330fd31cbae
v0.17.0 · stable
## [0.17.0] — in-app release notes + arm64 build targets

**In-app release notes.** The "vX.Y.Z available" pill in the status
bar now opens a modal that fetches and renders the release notes
inline instead of pushing the user to the browser. Backed by a new
`/api/notes/{version}` endpoint on the release server. The
"Download" button in the modal still opens the binary URL in the
system browser — auto-update is a separate, larger piece of work.

**arm64 builds.** `scripts/publish-all.sh` now accepts `linux-arm64`
and `windows-arm64` as opt-in platforms. The default invocation
still only builds amd64; arm64 must be requested explicitly:

```
scripts/publish-all.sh windows linux linux-arm64 windows-arm64
```

Both are **untested** — author has no native arm64 hardware to
validate on. Windows-arm64 cross-builds cleanly because CGO is off
on Windows; linux-arm64 requires a one-time `task setup:docker` to
fetch the Wails cross-compile image.

Per-platform binaries are now named with their suffix
(`ssh-tool-linux-amd64`, `ssh-tool-windows-arm64.exe`, …) when
uploaded so multiple platforms coexist in the same release.

---
linux-amd64 · ssh-tool-linux-amd64 21.1 MB · sha256 889d0dbd589b7209e57850fb1bd25113921e5ccb01b1f97d386161aa878496c0
windows-amd64 · ssh-tool-windows-amd64.exe 21.6 MB · sha256 028ee962cf935b29cf3c31f47a8e0eb9dd073823bbda832bd08ce03a5073d759
windows-arm64 · ssh-tool-windows-arm64.exe 20.0 MB · sha256 c48dacbf38dde84b7d99e8de4f24ab2fc8f4fd46b22cc93c0f0e5bcf5a32b523
v0.16.1 · stable
## [0.16.1] — relax FetchArchiveURL SSRF guard to metadata-IP only

v0.16.0's SSRF guard rejected loopback and RFC1918 alongside cloud
metadata, which broke legitimate use cases: catalog server on a LAN
(192.168.x.y), local dev catalog on 127.0.0.1. In a single-user
desktop app the attacker needs the user to paste a URL or click an
ssh-tool:// link — most internal targets are things the user is
actively asking for. Cloud metadata (169.254.169.254, fd00:ec2::254)
is the one IP the user would never knowingly fetch and an attacker
can't otherwise reach.

Guard now blocks only those two metadata IPs.

---
linux-amd64 · ssh-tool 21.1 MB · sha256 f1f5144fbd91ccc1885f480e696ea4ff01c74b1b5a65f1536d443867ea2f3b3f
windows-amd64 · ssh-tool.exe 21.6 MB · sha256 e102fcca8ac2c349b29008b1f29b3ad28073340d59b1a1b58ce0d0fc59f5216a
v0.16.0 · stable
## [0.16.0] — security batch (audit-driven)

Multi-agent security audit covering vault crypto, SSH/opkssh paths,
and the IPC surface. Fixes ranked **Critical** and **High** are in
this release; remaining Mediums tracked for follow-up.

**Critical**

- **Host-key algorithm downgrade closed.** `known_hosts` previously
  keyed on `(host, port, key_type)` so an active MITM serving a fresh
  RSA host key after the user trusted an ed25519 key landed as an
  ordinary "unknown host" prompt instead of "CHANGED". The schema
  collapses to one row per `(host, port)`; on connect we now pin
  `HostKeyAlgorithms` to the stored algo so the SSH handshake aborts
  if the remote tries to serve any other type. Migration 12 dedupes
  legacy multi-algo rows (newest wins).
- **`SshLaunchInSystemTerminal` command injection closed.** The
  Windows / macOS / Linux paths each interpolated `hostname` and
  `username` into a shell string, so a hostname like
  `evil.example;calc.exe` (e.g. imported from a malicious RDM JSON)
  ran code locally on click. Each platform now receives a real argv:
  `wt.exe new-tab -- cmd /k ssh …` on Windows, `osascript` with POSIX
  single-quote escaping inside the AppleScript literal on macOS,
  `bash -c` with the same shellJoinPosix on Linux.
- **SFTP DownloadDir path traversal closed.** A hostile remote
  feeding `..`/absolute paths through Walk could write outside the
  user-picked local root (e.g. into `~/.ssh/authorized_keys`).
  Joined paths are now Clean'd and bounded against the canonical
  root with a separator-aware prefix check.

**High**

- **HostKeyCallback no longer fail-opens on DB error.** A transient
  SQLite error (busy lock, disk full) previously caused the callback
  to return nil — silently accepting any host key. It now returns
  the error and aborts the connect.
- **Backup envelope authenticates its header.** v1 sealed the JSON
  envelope with nil AAD, leaving `magic`, `salt`, `nonce`, and the
  Argon2 parameters tamperable. A flipped `m=1<<31` would OOM the
  process on Restore. v2 binds the full header as AEAD additional
  data and sanity-bounds the KDF params before deriving. v1 files
  still decrypt for backward compatibility (re-create them under v2
  by running a fresh backup).
- **opkssh email redacted from logs.** Cert ValidPrincipals (which
  for opkssh is the user's OIDC email) was written cleartext to the
  desktop log on every connect and every cert refresh. Logs now show
  only the count.
- **FetchArchiveURL SSRF closed.** Catalog bundle fetches now refuse
  loopback, link-local, RFC1918, ULA, multicast, unspecified, and
  the cloud-metadata IP. Check runs in `net.Dialer.Control` on the
  actually-connected peer so DNS rebinding can't slip a public name
  past resolution.

**Notes**

- Schema migration 12 runs automatically on first launch.
- Existing v1 backups continue to decrypt; new backups are v2 and
  cannot be opened by older builds.
- Per-(host, port) known_hosts dedup keeps the newest row; if you
  see a CHANGED-key prompt for a host you trust, verify the
  fingerprint against the server before accepting.

---
linux-amd64 · ssh-tool 21.1 MB · sha256 3ee7896dd0dbde8f3ffe69739f31c4cff72c1d19132e4a091f26d7de79bbe593
windows-amd64 · ssh-tool.exe 21.6 MB · sha256 679d62d9c0598a158f1004a334809cca43dacf7d0a0731fbaa53d65925e1dd32
v0.15.2 · stable
## [0.15.2] — auto-close tab on user-initiated signal exits

`closeOnCleanExit` now also fires for exit codes 130 (SIGINT) and 143
(SIGTERM), not just 0. Background: typical "Ctrl+C then Ctrl+D"
sequence in bash exits the shell with status 130 (last command was
the SIGINT), which is still a user-initiated end — same intent as
`exit 0`, the tab should close. Real failures (`exit 1`, segfaults,
network drops) still keep the tab open.

---
linux-amd64 · ssh-tool 21.1 MB · sha256 4996a949231b88e50327d40da0cfdffd795230c8ec851a3d7fd494c4c6b0d50b
windows-amd64 · ssh-tool.exe 21.6 MB · sha256 34857722a006dcdeacc1a65197c3dde0e60868b7c36d6103b3b99884e7d4b16b
v0.15.1 · stable
## [0.15.1] — terminal: optional WebGL-renderer toggle

New Settings → Terminal toggle: **Disable WebGL renderer (use canvas
fallback)**. Workaround for sluggish keystroke echo seen on Linux
WebKit builds where the WebGL pipeline ends up on software GL
(`LIBGL_ALWAYS_SOFTWARE=1`). xterm's canvas renderer is often faster
in that setup.

Default off. Takes effect on newly-opened terminal tabs (existing
tabs keep whatever renderer they were created with — reopen to
apply).

---
linux-amd64 · ssh-tool 21.1 MB · sha256 8e2cb65224c0bbb6872292287659c742ac3d50e2357f7b4015dc187d45b5f183
windows-amd64 · ssh-tool.exe 21.6 MB · sha256 de40b78af404748b94bb69f9cbd2ac11b3a640345b5d6612865cfe69492a1d3d
v0.15.0 · stable
## [0.15.0] — five new dynamic inventory providers

Dynamic folders now speak DigitalOcean, Linode (Akamai), Vultr,
Scaleway, and AWS EC2 in addition to Proxmox and Hetzner. All reuse
the existing `api_token` credential shape — for AWS EC2 the
credential's `token_id` field doubles as the access key, the secret
holds the secret access key. Scaleway and AWS EC2 also need a
zone / region in the folder config (one folder per zone/region;
the APIs scope listings).

SigV4 is implemented inline for AWS rather than pulling
`aws-sdk-go-v2` — one endpoint (`DescribeInstances`) doesn't justify
that surface area.

The dynamic-folder editor learned a generic cloud-token form covering
all providers except Proxmox, with per-provider hostname-source
vocabularies (e.g. AWS adds "Public DNS", Linode/Vultr use "label"
where Hetzner/DO/Scaleway use "name") and per-provider token hints
pointing at the right control-panel page.

**Untested across providers** — the author doesn't have accounts at
any of the five. Feedback / bug reports welcome on any that misbehave.

---
linux-amd64 · ssh-tool 21.1 MB · sha256 734cb864a4cb729bbe9ad7386aa1e221332b43d9aaad4cdf458e81d016459a57
windows-amd64 · ssh-tool.exe 21.6 MB · sha256 af622cd898baf0eed8d90fd6bf866d3d6f8a022cf6777e94376ddde4826024ea
v0.14.0 · stable
## [0.14.0] — daily auto-backup

A background scheduler kicks off a backup at start and then hourly,
gated on "at least N hours since the last auto-backup file" (default
24h). Passphrase is recovered from the machine-bound auto-unlock
sidecar — if it isn't set up, the run is silently skipped so the
feature doesn't pester users who keep the vault locked across
restarts.

Auto-backups land in the same `backups/` directory as manual ones
but use a distinct `ssh-tool-auto-<ts>` prefix so pruning can leave
manual snapshots alone. After each successful run the pruner trims
auto-backups and `pre-restore-*` safety snapshots down to "Keep last
N" (default 7, max 365); manual backups never auto-delete.

Settings -> Backup & restore got an "Automatic daily backup" toggle
and a "Keep last N" number input. Changes flow through `SetConfig`
and the scheduler swaps its atomic config pointer without restart.

---
linux-amd64 · ssh-tool 21.0 MB · sha256 982ae7100787830f6c8c4bd8b18decb3f17efc144a81c78703ac7ffe9c6fe35f
windows-amd64 · ssh-tool.exe 21.4 MB · sha256 b09ab4d0d98741818547e46f70ba903a1485199f30b77e7872eddbbbaf692c4f
v0.13.0 · stable
## [0.13.0] — encrypted backup & restore

A new Settings -> Backup & restore tab takes an encrypted snapshot of
the SQLite store and the vault file with one click. The bundle lands
in <DataDir>/backups/, sealed with the user's vault master passphrase
(Argon2id 64 MiB / XChaCha20-Poly1305). The database is captured via
SQLite VACUUM INTO, so the snapshot is consistent even mid-write.

Restore is two-phase to work around the fact that SQLite holds the
live store.db open on Windows: the chosen backup is decrypted,
verified against its embedded SHA-256 checksums, snapshotted into
backups/pre-restore-<timestamp>/ as a safety undo, then staged in
<DataDir>/pending-restore/. The next app start applies the swap
before opening the database, drops stale WAL/SHM, and invalidates
the auto-unlock sidecar. The UI tells the user to quit and reopen.

Backups don't auto-rotate yet; manual delete from the list works.
Scheduled backups + rotation will come in a follow-up.

The passphrase prompt grew a password-masked mode along the way
(used by Create and Restore here, but available to other callers).

---
linux-amd64 · ssh-tool 21.0 MB · sha256 c08ff3fafd3ddc90e354d34ea9e2710344ca9c4e5efb3fb7b4c0e4437bc52b34
windows-amd64 · ssh-tool.exe 21.4 MB · sha256 7d008b4267e1b50166d847c7e7298d4c167f54c16000e0f19f4bc3151daa983b
v0.12.8 · stable
## [0.12.8] — vault: lock actually locks

The vault had a silent third storage layer alongside the in-memory
mirror and the encrypted file vault: the OS keychain (Windows
Credential Manager, macOS Keychain, Linux secret service). Every
credential secret was mirrored into it on save and read back as a
fallback on lookup. Because those OS stores stay unlocked for the
entire user login session, a "locked" vault still leaked every
secret on the next Get(). The Lock button and the idle auto-lock
looked like they were working while actually doing nothing.

This release removes the keychain entirely. The encrypted file vault
plus the master passphrase (typed or recovered from the machine-
bound sidecar at startup) is the only persistence path. Lock now
also wipes the in-memory plaintext cache, so Get returns nothing
until a real unlock. Saving a secret while the vault is locked is
no longer accepted — previously it would land only in memory, vanish
on the next restart, and silently break the user's expectation that
saved means persisted.

A one-shot migration runs once at startup to delete legacy keyring
entries under the old service name. Credentials whose secrets only
ever lived in the keyring (e.g. tokens saved while the vault was
locked under an earlier version) will need to be re-entered once.

Frontend: after a lock — manual or via the idle auto-lock — the
sidecar auto-unlock is suppressed for that re-prompt so the
passphrase modal actually appears instead of being bypassed in the
same tick. Subsequent app starts still auto-unlock as before.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 f1cf32627294ba38ed4d25a8f433de6f4140cdf4075ed83835c5b8595c218c13
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 300cb3badd18f9100aafa5f0bce581d69a616cc20e8c3afd1b35d20a969c0464
v0.12.7 · stable
## [0.12.7] — redock: bring back local PTY sessions

Companion to v0.12.6. Re-docking a detached window worked for SSH
tabs but a local terminal tab evaporated: the main window's
`window_redock` handler walked `SshActiveSessions` only, so when
the matched session id was a local PTY, the loop skipped it and
the user ended up with the detached window closed but no tab in
the main window.

Handler now also walks `LocalShellList` and re-adds those
sessions to both stores with `kind: "local"`.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 453525bad5e9d7aad51f245a185bcae9c07483d59ac6bcfb974f9b2b45274c57
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 ef7c04bc3f3e79ac71892076902526e66a969e7d7678079e84f953343ef19c42
v0.12.6 · stable
## [0.12.6] — detach: recover local PTY sessions too

The detached-window recovery loop only walked `SshActiveSessions`,
so tearing off a local terminal tab landed in a new window with
the tab strip rendered (session id visible) but no xterm canvas:
Terminal.svelte saw no entry in `sessions.tabs`, `isLocal()`
defaulted to false, and the SSH IPCs it tried for write/resize/
scrollback went nowhere.

DetachedWindow.svelte now also fetches `LocalShellList` and
adds each matching local session to both `sessions` and
`paneTabs` with `kind: "local"`, so Terminal.svelte routes
through `local_shell_*` IPCs.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 1d51cca0ba65a0ec0ad95cda107f6545aa9dd085c9006dad4c821f4e1ce07dcf
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 c4278591469ab04c830e1f031a4414f6c82d8a8f0bc92281f731c67a5022dda1
v0.12.5 · stable
## [0.12.5] — detached window: don't auto-close before tabs arrive

DetachedWindow.svelte had an auto-close $effect that fires when
`paneTabs.tabs.length` drops to zero (intentional for the Ctrl+D
session-cleanup path). Trouble was the same effect ran during the
window's first paint while sessions were still being recovered —
if recovery finished with zero matching tabs (e.g. the URL's
`?sessions=` list was empty or stale) the window slammed shut a
frame after open. Added a `hadTabs` latch so the auto-close only
arms after at least one tab has appeared.

Also defers wiring the WindowClosing hook on the detached
WebviewWindow until 500ms after creation, in case the Wails v3
alpha runtime fires a phantom WindowClosing during open on the
WebView2 build — would have torn down the freshly-transferred
sessions.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 b85528f20fa7327f962c311d33d901308114fe4f7683d35674552ae252490f21
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 9e79f6b476770e02593d535580756fde4845788655a43e0f2e857d13784d2750
v0.12.4 · stable
## [0.12.4] — single-instance hand-off scoped to deep links

v0.12.2 made every secondary launch hand off to the primary, which
broke detached terminal windows: Wails v3 spawns a child process
per detach and that process was exiting instantly because the
hand-off intercepted the launch. Now the single-instance code
only fires when this launch's argv actually carries an
`ssh-tool://…` URI or `--import-url=…` flag. Plain launches
(including Wails detach children) take the normal startup path.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 f398045f72a9945779b00e1afebdd05153e0b3decd1013a3682d3e5805ebd8b5
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 e9df077a28eeb08dc4bf6d4dadae352a4052c538ea0a2356b365d317392f4a58
v0.12.2 · stable
## [0.12.2] — single-instance handoff

Companion to v0.12.1. Two issues from real-world deep-link use:

### Single instance

Launching ssh-tool while it's already running used to spin up a
fresh process — second window, fresh tab pool, the deep link
landing in the new instance instead of the one the user already
has open. Now the second process detects the primary via a
loopback TCP listener (port persisted in `DataDir/instance.lock`),
hands off its argv + exits. The primary re-emits
`deep_link_import` and refocuses its window so the catalog
"Open in ssh-tool" click feels instant on subsequent uses.

Falls back to "act as the primary" if the lock file is stale
(port unreachable) so a hard-killed previous run doesn't trap
fresh launches forever.

Loopback-only, no auth on the wire — we trust anything that can
already write to `DataDir/`.

### Deep-link handoff

`startInstanceServer` runs alongside the cold-start dispatcher
so the same `parseDeepLinkArg` codepath fires either way: the
`deep_link_import` event lands in App.svelte with a 200ms delay
(vs 1200ms on cold start) and Settings → Import archive picks
it up via the existing deepLink store.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 5380463c0ae1dd7f7d36b8ee62264c6d7b58298304a56ab67f684536a1a9982c
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 4417422186c8153fa3b3a996ea84e255a5bb75acaff7970d441a8012c0573792
v0.12.1 · stable
## [0.12.1] — register ssh-tool:// URL handler

Companion to v0.12.0. The deep-link feature only works if the OS
knows where to launch when a browser hits `ssh-tool://…` — and
nothing previously wrote that association.

### Features

- **`Register handler` button** in Settings → Import archive.
  Calls a new RegisterURLScheme IPC that writes the per-user OS
  registration:
  - Windows: `HKCU\Software\Classes\ssh-tool\…` keys pointing at
    the current exe.
  - Linux: `~/.local/share/applications/ssh-tool-url.desktop` +
    `xdg-mime default x-scheme-handler/ssh-tool`.
  - macOS: returns "not supported" — bundling the .app with the
    proper `CFBundleURLTypes` is the right way; runtime
    registration via private LaunchServices APIs would be fragile.
- **Status row** above the URL fetcher shows whether the handler
  is registered and, if so, the exact launch command it'll use.
- No admin elevation on Windows; no sudo on Linux.

After registering once, clicking "Open in ssh-tool" in the catalog
launches this app with the bundle URL pre-filled and the dry-run
preview ready.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 dfc9fa599506be7ed765fc33ab00030a3c958143f0d5985545ce5fa2c87ef702
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 476d0968c6bd55fc028f30af2a1490ece8cf1bd419fadf6c10046a4aba3ae053
v0.12.0 · stable
## [0.12.0] — deep-link import from ssh-tool-catalog

### Features

- **`ssh-tool://import?source=URL` deep link.** When ssh-tool is
  launched with a deep-link arg (either the URI form via an OS
  protocol handler, or `--import-url=URL` via the command line),
  the app routes to Settings → Import archive, pre-fills the URL
  field, and fetches the archive automatically. The catalog gets
  a new "Open in ssh-tool" button per entry that fires this URL
  via a hidden iframe — works in Chrome / Firefox / Edge once the
  scheme is registered locally.
- **Import from URL** added to Settings → Import archive. The URL
  field sits above the archive textarea, with a Fetch button that
  hits the catalog's `/api/bundle?ids=…` endpoint (or any
  http(s) URL returning an archive payload). Fetched content
  lands in the textarea so dry-run / conflict mode still apply.
- New `FetchArchiveURL` IPC backs this: 20-second timeout, 10 MiB
  body cap, http(s)-only scheme guard. Server-side fetch sidesteps
  WebView CORS for catalog URLs that don't add CORS headers.

### Notes for protocol handler registration

Windows / macOS / Linux all need a one-time OS-level association
between `ssh-tool://` and the executable. The packaging step that
emits installers is the natural place to wire that up — until
then, the `--import-url=…` CLI flag works as the always-available
fallback.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 de82f16ca10b3ce1c2fecf8711f11b60d6bafb8918caabcc95840e744df417c4
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 b0d708e2d8eadbe3c8e57da0aaf38ccd9e48174097f8f55a32dea3b336408e2c
v0.11.0 · stable
## [0.11.0] — export port forwards + SOCKS bookmarks

### Features

- **Port forwards travel with the archive** now. Previously
  `Export folder → TOML/JSON` shipped only folders, connections,
  and credentials. The receiving ssh-tool re-created hosts but
  every forward + every SOCKS bookmark had to be rebuilt by hand
  — the most painful part of any cross-machine setup, since the
  bookmarks are typically internal URLs you'd never remember.
- Archive `Archive` struct gets a `forwards[]` block. Each entry
  carries kind / local addr+port / remote host+port / auto_start
  / description / bookmarks[]. SOCKS5 dynamic forwards round-trip
  with their full bookmark list inline.
- Importer reads `forwards[]` and recreates the rows under the
  newly-imported (or matched-existing) parent connection. Bookmark
  array applied via `SetPortForwardBookmarks` once the forward row
  exists. `ImportSummary` grows a `forwards_created` count.

### Notes for sharing via catalog

The companion catalog service (`ssh-tool-catalog`) detects the new
`forwards[]` block when you upload an archive and surfaces a small
chip on the entry card: `3 fwd · 5🔖`. Older archives without the
field are still accepted as-is.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 5d15451075d42415c494330f8233dbc985d69a25fa71bb7e7b9708e31c47e5ac
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 6d0fd349c8bfeded9e15daf524c5b095bad46fa49fd5853f47e00fbfa1421399
v0.10.0 · stable
## [0.10.0] — editable API token credentials

### Features

- **API token credentials are now fully editable** in the
  Credential detail pane. Previously only the metadata
  (name / hint / default username / icon) could be changed and
  the secret was write-once. Now:
  - **Token ID** field — for providers that use one
    (Proxmox: `user@realm!tokenid`). Hetzner Cloud leaves it
    blank.
  - **New token secret** field — type a new value to rotate;
    leave empty to keep the current one.
  - **Show token secret** button reveals the current secret in
    a read-only box (auto-hides after 30s), same UX as
    passwords and private keys.
  - "Save changes" is enabled when either field has a change;
    saving updates both in one call and bumps
    `last_rotated_at` when the secret moves.

### Backend

- New `CredentialsRotateAPIToken(id, token_id?, new_secret)`
  IPC + `creds.Service.RotateAPIToken` implementation. The
  token id passes through `UpdateCredential` (config map); the
  secret goes through the standard vault put + history append.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 e63d0ce2f4c5326ff749560231bc2b8f75fa8586c71ccbf67fabff473a838832
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 521d5c77f496d50180f039458427943cd985297667a2ceb04ffa0bb071084547
v0.9.0 · stable
## [0.9.0] — dynamic entry detail pane + chevron fix

### Features

- **Click a dynamic entry, get the info ploča.** Previously a
  click on a Proxmox VM / LXC or a Hetzner server in the tree
  did nothing — only double-click connected. Now a single click
  selects the entry and renders a read-only DetailPane:
  - Header with name, status pill, provider chip, "from
    {folder}" breadcrumb.
  - Action buttons: Connect (with stopped-VM confirm),
    Copy host, Refresh inventory.
  - Facts grid with name, hostname, kind, plus provider-
    specific fields. Proxmox: resource type, hosting node,
    VMID, vCPUs, memory, disk, uptime. Hetzner: server id,
    server type, datacenter, image, public IPv4 / IPv6,
    private IPv4, created.
  - Tags / labels row.
  - Live load bars (CPU / Memory / Disk) for Proxmox, colour-
    coded by usage. Hetzner doesn't expose live load on the
    /servers endpoint, so the section is hidden there.
  - Collapsible "Raw provider payload" with the JSON the
    provider returned, for everything not covered above.

- **Proxmox payload widened.** The Proxmox provider now keeps
  `maxcpu / maxmem / maxdisk / cpu / mem / disk / uptime` from
  `/cluster/resources`, so the detail pane can show real numbers
  without an extra round-trip.

### Fixes

- **Chevron now appears on dynamic folders.** The tree was
  computing `hasChildren` only from manual children (folders +
  connections), so a dynamic folder with 50 entries but no
  manual rows lost its `▸ / ▾` arrow — you could expand it via
  double-click, but there was no visual hint anything was
  there. Now dynamic entry count (or the not-yet-loaded state)
  counts as having children too.

### Tech

- `DynamicEntry.Raw` switched from `[]byte` to
  `json.RawMessage` so the JSON IPC layer surfaces it as a
  parsed object on the frontend instead of base64.
- New `Selection` kind: `dynamicEntry` with
  `{ folderId, entryId }`. ViewStore clears it when switching
  to the Credentials tab.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 3a38878b668231f5756ee0f9883ca7bf4ff6aa3aa49966e835e06cadf9f7c791
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 655ae299a1bcb6b51bc9c085aabbc63a89996e9150d3564cc768f76ff5738160
v0.8.1 · stable
## [0.8.1] — dynamic folder UX: optional token id + visual accent

### Fixes

- Inline "+ New token" form inside the dynamic-folder editor
  refused to save without a token id, but Hetzner Cloud tokens
  don't have one. Made the id optional (the standalone
  Credentials → New flow already accepted it as optional).

### UX

- **Dynamic folders look different** in the tree now:
  - Globe icon instead of folder.
  - Folder name + icon tinted teal so they stand out from
    regular folders at a glance.
  - Provider pill (`proxmox` / `hetzner`) sits inline next to
    the name.
  - A red `!` dot appears when the last refresh errored —
    hover for the message.
  - Tooltip on the name shows provider + "refreshed Nm ago"
    + last error if any.
  - Child count badge counts the cached dynamic entries
    instead of (empty) manual children.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 2666311a9c6273674f7d9108040e08b66261c8ef726c8b9e45ab0d9c3655faa8
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 fdc3d88cf3e1f876669729ba3150334c20e454f7963bbfa4f65b3d67ae2c5eb3
v0.8.0 · stable
## [0.8.0] — Hetzner Cloud dynamic inventory

### New

- **Hetzner Cloud provider** for dynamic inventory. Pick
  "Hetzner Cloud" in the provider dropdown when creating a
  dynamic folder; supply a Cloud Console API token (any
  permission level — read is enough for inventory). Pulls
  every server (paginated `GET /v1/servers`), maps them as
  guests, and respects the same hide-stopped + label
  whitelist/blacklist filters as the Proxmox provider.
- **Hostname source picker** (Hetzner specific): Cloud
  doesn't auto-DNS, so pick whether to connect by server
  name (relying on your own DNS), public IPv4, or first
  private-network IPv4.
- Labels arrive as `key=value` tag strings, so the whitelist /
  blacklist match them literally — `env=prod` and so on.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 688afdcceb7deb7dead9303e7b7d85ecd518b8b274351f1f1196eba780769fa6
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 7c12b5ae81c032899cf01382607a968270a0a74d8da0d660a2cba66e729e4bc7
v0.7.5 · stable
## [0.7.5] — info.json mutated tree, fixed via generated copy

### Fixes

- The Win-version-injection landed in v0.7.4 rewrote
  `build/windows/info.json` in place every build, so the next
  build always saw a dirty tree and stamped `-dirty` into the
  binary version (visible especially in the Linux build, which
  ran second in the publish-all chain). Switched to writing
  `build/windows/info.generated.json` (gitignored) next to the
  template and pointing wails3 syso at the generated copy.
  Source `info.json` stays as the canonical template.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 1e34576190cf4881677ec48a1d0e4dcc6d8e8b667f0d0c5fcd9b0b79a6316985
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 36d866d4db90d789c62d87fd9c02fa824ba760a27e229764ae3c11c445c4d687
v0.7.4 · stable
## [0.7.4] — Linux binaries + correct Win .exe version metadata

### Fixes

- The Windows `.exe` Properties dialog reported version `0.1.0.0`
  on every build because `build/windows/info.json` hard-codes the
  template default and Wails embeds it as-is into the
  `VERSIONINFO` resource. The build chain now rewrites
  `file_version` + `ProductVersion` from `git describe` right
  before `wails3 generate syso` runs, so the Properties dialog
  matches the actual release.

### New

- **Linux amd64 build + publish path.** `task linux:build`
  produces `bin/ssh-tool`; `scripts/publish-all.sh` builds and
  uploads every platform in one go (Windows + Linux today;
  darwin scaffolded but not enabled by default). The release
  server's `/api/latest` now serves a `linux-amd64` asset
  alongside `windows-amd64`, so the in-app update check picks
  the matching binary per host.

---
linux-amd64 · ssh-tool 20.9 MB · sha256 0f8c169b799ebcc4e803c7e36b055ca682be131b982d11864e6fba57a72718b5
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 c65c97ddb66e61c5702bb4a370cf15b94d901758b9c1041625b5dc2d91cdf514
v0.7.3 · stable
## [0.7.3] — search reaches dynamic-inventory entries

### Fixes

- **Ctrl+K palette** now indexes every cached entry of every
  dynamic folder alongside regular connections + folders. Pre-
  fetches uncached folders when the palette opens so a search
  hits VMs in dynamic folders the user hasn't expanded yet.
  Selecting a dynamic entry goes through `SshConnectDynamic`;
  stopped guests still get the "connect anyway?" confirm.
- **Sidebar quick filter** now walks dynamic entries during
  `folderHasMatch`, so a dynamic folder auto-expands and
  surfaces matching VMs when the filter narrows. First
  keystroke of the filter eagerly pulls entries for all
  dynamic folders that haven't been opened yet, so the search
  isn't stuck on an empty cache.

---
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 e02b28856a20d9aa23624e2863a73088304ff5a46aab9aa1b85ef8613262bf28
v0.7.2 · stable
## [0.7.2] — API token kind in the Credentials view

### Fixes

- Credentials → New… didn't list the new `api_token` kind, so
  the only way to create one was the inline "+ New" in the
  dynamic-folder editor. Added the option to the kind dropdown
  in CredentialCreate plus an icon (`globe`) in the tree.

---
windows-amd64 · ssh-tool.exe 21.3 MB · sha256 a2a1853763f86fc8ed3ef7fa60ecd801c1d9e2e7284f8cb0379ca85acd47fc0c
v0.7.1 · stable
## [0.7.1] — dynamic inventory polish: token-as-credential, hide-stopped

### New

- **API tokens live in the credentials vault.** A new credential
  kind `api_token` stores `{ token_id, secret }` with the secret
  in the vault. The dynamic-folder editor binds to a credential
  by reference (`api_token_credential_id`) instead of holding the
  secret inline. Side effects:
  - Same token can back multiple dynamic folders.
  - Secret rotation goes through Credentials → that token —
    nothing to re-enter in the dynamic-folder editor on every
    save.
  - On-disk dynamic_folders config no longer carries any secret
    material.
  - Editor has a "+ New" inline form so first-time setup is
    one screen.
- **Hide stopped guests** toggle in the dynamic-folder editor.
  Server-side filter — stopped guests are dropped from the cache
  entirely when set, freeing the tree from clutter on
  short-lived-VM clusters. Hosts are always kept (their
  "online"/"unknown" status is uninformative for this filter).
- **Stopped guests remain searchable** when the hide toggle is
  off: the name filter walks all cached entries before bucketing,
  so a query matches greyed-out stopped rows too.

### Internals

- Manager.resolveSecrets() inlines the resolved secret into a
  copy of the provider config just before Fetch. Providers stay
  unaware of the credential-reference indirection.
- New CreateCredential kind in creds.Service (`createAPIToken`).

---
windows-amd64 · ssh-tool.exe 21.2 MB · sha256 33a2948a9ff8bf24256cc6541c9c10f7c722321696063318f25a3a10cee80d11
v0.7.0 · stable
## [0.7.0] — dynamic inventory (Proxmox)

### New

- **Dynamic inventory folders**: folders whose children are pulled
  from an external source instead of stored by the user. Tree
  right-click → **New dynamic folder…** or the empty-area menu's
  third option opens the editor. Children render under the same
  inherit cascade as regular folders, so the credential / port /
  jump host you set on the folder apply to every VM listed under it.
- **Proxmox VE provider**:
  - Single GET to `/api2/json/cluster/resources` per refresh —
    one call covers every VM, LXC, and node from the whole
    cluster. Works through a load balancer (no per-node
    failover logic needed).
  - PVE API token authentication
    (`user@realm!tokenid` + secret).
  - Optional self-signed TLS skip.
  - Include hosts (PVE nodes), guests (qemu + LXC), or both.
  - Tag whitelist + blacklist filters.
- **Hosts / Guests pseudo-sub-folders** under a dynamic folder
  group entries by kind so the tree stays readable on clusters
  with 100+ VMs.
- **Stopped-guest UX**: rendered greyed out with a "stopped"
  badge; a connect attempt prompts "Connect anyway?" since the
  VM may still be reachable on another address.
- **Refresh model**: per-folder configurable timer (default 5
  min, 0 disables) + **Refresh now** right-click action + lazy
  load on first expand. Errors surface in the folder editor's
  info panel; the previous good entry list stays visible
  during transient outages.
- **DNS-based hostnames**: VM `name` is used directly as the
  hostname (matches the user's setup where every VM/LXC has an
  FQDN A-record).

### Internals

- New `internal/inventory` package: `Provider` interface,
  `Filter` helper, `Manager` (timer + cache orchestration),
  Proxmox provider implementation. Roll-own HTTP client, no
  external dep.
- Migration 11: `dynamic_folders` + `dynamic_entries` tables.
  Folder row lives in the regular `folders` table so inherit
  cascade keeps working; the dynamic side data is a JOIN on
  `folder_id`.
- New IPCs: `DynamicFolderCreate / Update / Get`,
  `DynamicFoldersList`, `DynamicFolderRefreshNow`,
  `DynamicEntriesList`, `SshConnectDynamic`. The connect path
  constructs a synthetic `store.Connection` in memory and
  feeds it through the standard resolver + SSH layer — no
  persistent connection row.

---
windows-amd64 · ssh-tool.exe 21.2 MB · sha256 63d543e9b5793c19d88994fd080983af40cafe844726ff2193381fe31d9d9984
v0.6.3 · stable
## [0.6.3] — reproducible build + missing v0.6.1 release note

### Fixes

- `go mod tidy` was wired as a parallel dep of `generate:bindings`
  in the Wails Taskfile. Tidy walks the whole module tree
  including `frontend/`, racing against the concurrent
  `npm install` that adds/removes platform-specific
  `node_modules/` folders (fsevents, rollup-darwin, etc.). Every
  `task windows:build` invocation hit the race and either failed
  outright or stamped `-dirty` into the binary version. Dropped
  tidy from the build chain — run it manually when `go.mod`
  changes.
- v0.6.1 had no dedicated changelog hunk because the publish
  script extracts `## [vX.Y.Z]` by exact version match. Backfilled
  one (it was a re-tag of v0.6.0 after a clean `go mod tidy`).

---
windows-amd64 · ssh-tool.exe 21.2 MB · sha256 fc010a1266f44c3dcd7cd8f9c1ff83e5ddb881b8a1c864bc74e358e07fe888bc
v0.5.5 · stable
## [0.5.5] — WSL as external terminal option

### New

- **WSL (default distro)** added as a fourth external-terminal
  choice on Windows. Native terminal button opens `wsl.exe`
  (preferring Windows Terminal as the host); Open in external
  terminal connection action wraps the resolved
  `ssh user@host -p … -J …` in
  `wsl.exe -e bash -lc "ssh …; exec bash"` so SSH uses the WSL
  distro's OpenSSH client, `~/.ssh/config`, and `known_hosts`.
  Useful when your WSL side already has SSH keys, agent,
  jumphosts, and identities the Windows OpenSSH client doesn't.
- macOS / Linux native terminal launchers were already correct
  for the OS default; the WSL choice has no effect there.

---
windows-amd64 · ssh-tool.exe 21.1 MB · sha256 61b1b57a0005ebe1aa78ec25213b9761314bd0fe5b9294260b8d2546c04deec9
v0.5.4 · stable
## [0.5.4] — opkssh credential exposes all config fields

### New

- **Edit provider hint, max cert age, and refresh threshold** on
  the credential detail panel for opkssh credentials. Previously
  only the provider YAML + key basename were editable; the
  other knobs were locked in at create time. Each input gets a
  short hint explaining what it does and the default.

---
windows-amd64 · ssh-tool.exe 21.1 MB · sha256 9790d8729e7de73adbd59405f045de6d4bce2615c3d2d41baba97e80b48c6e18
v0.5.3 · stable
## [0.5.3] — Updates moved to its own Settings section

### Fixes

- Update check setting lived under Settings → Connection, which
  was the wrong home — it's an app-level toggle, not a
  connect-time tunable. Promoted it to a dedicated **Updates**
  section under a new **App** group in the side nav.

---
windows-amd64 · ssh-tool.exe 21.1 MB · sha256 70bd7281e68e68dcb5ebd2dd8799ba0619c17087cfb2b6b9b5647bd821bc379a
v0.5.2 · stable
## [0.5.2] — UI font size actually resizes, Appearance toggles styled

### Fixes

- **UI font size buttons did nothing.** App.svelte had a
  hardcoded `font-size: 14px` on html/body via `:global()` which
  beat the `var(--ui-font-size)` declaration that the setter in
  appPrefs flips on :root. Switched to
  `font-size: var(--ui-font-size, 13px)` so changes flow through.
- **UI font size editor**: bare `<input type="number">` replaced
  with `−` / `+` / Reset buttons. The input + onblur path was
  unreliable (stale parseInt could push NaN past the clamp and
  the apply() call silently dropped). Hardened setBaseFontSize
  to reject non-finite values, round, and re-apply even on
  no-op so reload edges still push the CSS var.
- **Appearance toggles** (color tag as row background, emphasise
  active session row, tab uptime timer) were bare checkbox
  labels — looked nothing like the bordered card checkboxes
  used for tray / external terminal / update check elsewhere
  in Settings. Converted to the same `.check-cards` fieldset
  so checked state shows the blue border + tinted background.

---
windows-amd64 · ssh-tool.exe 21.1 MB · sha256 398f3c21d9b6528f2657fc7adc5021c99cf4caa699071c0272aee15d254058e9
v0.5.1 · stable
## [0.5.1] — update check, type-to-search, PS/cmd console fix

### New

- **Auto update check.** The app polls
  `https://sshtool.app/api/latest` 5 s after launch and then
  every 6 hours. A green "↑ vX.Y.Z available" pill appears in
  the status bar when a newer release is out; click opens the
  changelog. Default on, opt-out under Settings → Connection →
  Updates. With the toggle off no HTTP request leaves the app.
- **Type-to-search in the tree.** With the connections tree
  focused, hit any printable character and focus jumps to the
  search input, with the keystroke preserved (no first-char
  loss). Modifier-prefixed shortcuts (Ctrl+C copy, Cmd+A, etc.)
  are skipped so existing keybindings keep working.
- **Search row promoted to the top** of the sidebar, above
  Quick access and Tag filter. It's the most-used entry point.
- **Escape clears the filter** from anywhere in the tree — not
  just while focus is in the search input. Useful after
  ArrowDown / Enter moves focus into a row.
- **`scripts/publish-release.sh`** ships a binary to the
  release server: pulls version from `git describe
  --exact-match`, computes sha256 locally, extracts the
  matching CHANGELOG hunk, posts a multipart form with the
  bearer token from `$RELEASE_TOKEN` or
  `~/.config/ssh-tool/release-token`.

### Fixes

- **PowerShell / cmd external terminal launches no longer flash
  and disappear.** A GUI-subsystem process spawning shells
  directly leaves the child with no console host. Wrapped both
  the Native terminal button and the Open in external terminal
  connection action in `cmd /c start "" <shell>` so Windows
  allocates a new console and detaches the child cleanly.
  Windows Terminal was unaffected (ships its own conhost).

---
windows-amd64 · ssh-tool.exe 21.1 MB · sha256 251665cfe14ed3e05fb2e4c35b1ec19bba481c00e198867f3710dabeb08f9db6