Termix-SSH / Termix-SSH/Support

[BUG] 2.7.1: A changed host key leaves a host silently stuck at "Reachable" — status carries no reason

Open
#1,266 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug host-metrics platform-desktop platform-docker platform-macos security ssh ui
Dominant language
No language data
Stars
28
Forks
4
PR merge metrics
No merged PRs in 30d

Description

2.7.1: A changed host key leaves a host silently stuck at "Reachable" — the status carries no reason, so the user only discovers it when opening a terminal by hand

Platform

App - macOS (Apple Silicon) with a self-hosted server doing the polling; the logic is server-side, so all clients are affected

Server Installation Method

Docker

Version

Desktop app 2.7.1; self-hosted server 2.7.1

Troubleshooting
  • I have examined logs and tried to find the issue
  • I have reviewed opened and closed issues
  • I have tried restarting the application
  • I have checked open issues and ensured this is not a duplicate
The Problem

After rebuilding/renumbering several machines, their SSH host keys changed. Termix correctly refuses the metrics connection — but nothing about that reaches the UI. The affected hosts simply sit at Reachable forever, indistinguishable from a host that merely has not been authenticated yet. In my case 4 of 29 hosts were in this state and I only found out by opening a terminal to one of them by hand, one at a time.

Server log (2 hours, stats_connect_failed):

[ERROR] [🖥️] Host key mismatch detected - SECURITY WARNING
{ operation: 'host_key_mismatch', hostId: 3, ip: '192.168.1.168', ... }
[ERROR] [🖥️] Host key changed - please connect via Terminal to verify the new key
[ERROR] [📊] Stats collector connection failed [op:stats_connect_failed,host:3]
Error: Host denied (verification failed)

25 such rejections in two hours, repeating every poll cycle forever.

Two things combine to hide this:

  1. The status entry has no reason field. statusStore.set() writes only { status, lastChecked }, and GET /status returns just that. There is no place to put "why not authenticated", so no client can render it.
  2. A host-key rejection is not even classified as a failure. In the metrics collector's error handling, authFailureTracker.recordFailure() is called for AUTH (No password available, Permission denied, All configured authentication methods failed, …) and for TIMEOUT, but Host denied (verification failed) matches none of those branches. It falls through unclassified, so the host is retried at full rate indefinitely and is never marked as needing attention.

The result is that the one host state that genuinely requires a human decision — the machine's identity changed, please verify it — is the one state the UI never surfaces. It also trains the wrong instinct: the user eventually opens a terminal, is asked to accept a new fingerprint, and accepts it just to make the host work again, without any context on when or why it changed.

How to Reproduce
  1. Add a host and let Termix store its key (status goes to Available).
  2. Regenerate that machine's SSH host keys (e.g. rebuild the VM/container, or rm /etc/ssh/ssh_host_* && dpkg-reconfigure openssh-server).
  3. The host drops to Reachable and stays there. No badge, no warning, no indication in the host list; the server log fills with host_key_mismatch / Host denied (verification failed) every poll cycle.
Additional Context

Suggested fix, roughly in order of value:

  • Add a reason/detail field to the status entry (e.g. { status, reason: 'host_key_changed', lastChecked }) and surface it in the host list — a distinct badge such as "Host key changed — verification required" rather than the generic Reachable.
  • Classify Host denied (verification failed) in the metrics collector's error handling and back off, instead of retrying at full rate forever.
  • Offer a way to review and accept the new fingerprint from the host entry itself, showing old vs new, instead of requiring the user to open an interactive terminal to each affected host.

Related: the generic Reachable label is also unlocalized and truncated in the list — filed separately as #1265.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with statusStore.set(), GET /status, the metrics collector error handling, and authFailureTracker.recordFailure(), then trace how the host list renders status. Done means host-key verification failures are classified and no longer retried indefinitely, while the status returned to clients exposes a clear verification-required reason.

Written by the indexing model from the issue text.

Assessment

Tech stack
docker
Domain
authentication, backend, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.