Skip to content

v4.2.0

Latest

Choose a tag to compare

@bvisible bvisible released this 13 Nov 18:06
· 1 commit to main since this release

A Chinese interface, a fuller audit log, and a guard against raw ssh. Nothing
to change on upgrade: the guard and the background mode are off unless you use
them, and the audit log only gains lines when AUDIT_LOG is already on (the
release pipeline upgrades 3.8.5, 3.8.6, 3.8.7 and every 4.0.x and 4.1.x from the
registry to this version and back). Two things do behave differently without
being asked: a tool that fails now sets isError in its reply, and the interface
follows a Chinese system language until you choose one.

The Windows installer is still not Authenticode-signed, for the same reason
as 4.0.0: Windows shows "Windows protected your PC", More info → Run anyway,
and ssh-manager app checks the installer against the SHA-256 in
release-manifest-win32.json. macOS builds are signed and notarized.

Many of these came from reading what the forks of this project changed: thanks
to @jaredshuai, @SEU-zhc, @fyq163 and @xutianhao6 for the work and the ideas.

Added

  • ssh-manager codex setup and ssh-manager codex status. setup points
    OpenAI Codex at this engine: only the ssh-manager table of
    ~/.codex/config.toml is replaced, the rest of the file stays, the entry names
    the npm engine, and a second run changes nothing; without an npm install it says
    how to get one and writes nothing. status says what Codex runs today. They were
    documented (with migrate, test and convert, which have no meaning with the
    V4 engine; ssh-manager export servers.toml writes the servers as TOML) and none
    of them existed. The entry that ConfigLoader.saveToCodexConfig wrote named
    <current directory>/src/index.js, a file that exists only in a source checkout,
    so for an npm install Codex started no engine; it now names the module's own
    folder. Found thanks to @jaredshuai's fork.
  • A Chinese interface (简体中文). Options → Appearance has a Language / 语言
    selector, for the desktop application and the web interface; the desktop menus,
    dialogs and notifications follow it. Until you choose, the interface follows the
    system language (Chinese for a zh locale, English for anything else), and the
    choice is remembered. English is exactly as it was. Server names, commands,
    paths and what a remote host prints are never translated. The translation is
    @xutianhao6's, from his fork's community build: adapted here, completed for the
    strings it missed and proofread. Thank you.
  • ssh_execute with background: true starts a command detached from the
    SSH session and returns at once with { pid, logFile }, for a job that
    outlives the timeout (a training run, a long build). Read its output with
    ssh_tail (follow: false), check or stop it with ssh_process_manager.
    The log file is never chosen by the caller: a free path would let a command
    that passes the readonly policy overwrite any file the SSH user can write,
    so the engine creates a private one with mktemp. The launcher works under a
    csh or tcsh login shell; Windows targets are refused with a clear error. The
    idea is @SEU-zhc's.
  • ssh-manager guard, an opt-in hook for a coding agent's shell tool that refuses (or, with
    --mode ask, asks about) ssh, scp, sftp and a remote rsync run from the
    agent's own shell, where the approval, the policy and the audit log do not
    apply, and tells the agent which MCP tool to use. install edits only its own
    entry in the agent's user settings file (a copy is kept the first time), remove
    restores the file, print and status write nothing. It reads the command
    text: a guard against accidents, not a security boundary. The idea is
    @fyq163's, whose hook only reminded.
  • SSH_LOG_STDERR=false keeps the engine silent on stderr, for a client that
    draws a terminal interface on that stream (OpenCode and Kilo were the
    reports). The log file still receives the log lines; nothing changes unless
    the variable is set. The idea is @fyq163's.

Changed

  • With AUDIT_LOG set, every tool that targets the server records its
    outcome
    , not only a denial or an approval decision: ssh_tail, ssh_sync,
    ssh_deploy, ssh_db_*, ssh_backup_*, ssh_process_manager and the rest now
    leave a line with success, and error when they failed. A tool that records
    its own exit code still writes exactly one line. Tools that only read local
    state (server list, aliases, tunnel list) leave none. The design is
    @jaredshuai's.
  • A tool that fails now says so in the MCP reply: the 36 tools that answered
    "❌ … failed" as plain text now also set isError, which is what a client
    expects (and what the audit log needs to tell a failure from a success).

Fixed

  • A failed dump no longer reports success. mysqldump … | gzip > file (and
    pg_dump) reports the exit code of its last command: with a wrong password, a
    missing database or a full disk the dump died, gzip still succeeded, and
    ssh_db_dump and ssh_backup_create answered success with an empty archive of
    20 bytes. The cron script made by ssh_backup_schedule did the same every night,
    and its retention cleanup then deleted the good archives. A dump that fails now
    fails, with the dump program's own message, and leaves no file; the dump still
    streams, with no temporary file. The cron script has set -o pipefail, removes a
    failed archive and stops before the cleanup. Found thanks to @jaredshuai's fork.
  • A corrupt archive is no longer restored in part. gunzip -c dump.gz | mysql
    ran the database program on whatever gunzip had read, so a truncated archive was
    restored half-way with no error. A gzip file is now tested whole before
    ssh_db_import or ssh_backup_restore start the database program (MySQL and
    PostgreSQL). A plain dump under a .gz name, which a backup made without
    compression used to leave, is still restored.
  • A MongoDB backup can be restored. It was written as <id>.tar.gz but
    measured and restored as <id>.gz: a size of 0 and no way back. One place now
    knows the path, a restore checks the file is there before it starts, and
    archives made by older versions (they were already <id>.tar.gz) become
    restorable. ssh_db_dump reports the path it really wrote.
  • ssh-manager server remove prod removes prod, not prod_eu too. The CLI
    matched a server's lines by the bare prefix SSH_SERVER_PROD_, which every
    longer name starts with, so the other server lost its host, user and password.
    Lines are now matched by the setting names the engine reads. Found thanks to
    @jaredshuai's fork.
  • Editing a server from the CLI keeps its settings. It deleted all the
    server's lines and wrote seven back, so a read-only server became unrestricted:
    its mode, allow and deny patterns, audit log, group and proxy jump were dropped.
    Only the settings the wizard asks for are replaced now, in place.
  • A password with a # survives the CLI. ssh-manager server add wrote
    a#b c"d bare, and the engine read it back as a (dotenv starts a comment at
    any #). A value is quoted only when dotenv would misread it, so every other
    value is written exactly as before; one that cannot be carried is refused with a
    reason and nothing is written.
  • The health check no longer counts container layers as disks. On a Docker
    host every overlay layer repeated the same usage, and tmpfs, devtmpfs and
    squashfs mounts counted as full disks (a snap mount is always at 100 %), which
    raised duplicate or false disk alerts. The check now records each mount's
    filesystem type and skips those, keeping / even inside a container, and an
    alert fires when usage reaches the threshold, not only when it passes it. In the
    interface, the health card keeps long mount paths and alert text inside its
    borders. Thanks to @xutianhao6.
  • Remote tunnels (ssh_tunnel_create with type: "remote", ssh -R) never
    worked
    : they failed with this.ssh.forwardIn is not a function, because the
    connection wrapper only had forwardOut. They work now, and closing one
    cancels the forward and removes its listener. A failed creation is reported as
    an error. Found thanks to @jaredshuai's fork.
  • Six simultaneous calls on a cold engine opened six SSH connections, all kept
    open and five of them unreachable, a burst of logins that a server's
    MaxStartups or fail2ban can refuse. Callers now wait for the connection being
    established; a failed attempt is not remembered. Found thanks to
    @jaredshuai's fork.

Security

  • A database password no longer reaches the audit log or the approval request
    in clear text.
    dbPassword and db_password (taken by the backup, restore,
    dump and import tools) were missing from a list of names to redact, so a
    refused or approved call wrote the password into the audit file and showed it
    in the approval request. The audit log now redacts any key that looks like a
    secret, with the same rule as the logger. Found thanks to @jaredshuai's fork.

Install or update: npm install -g mcp-ssh-manager@4.2.0 (or npm update -g mcp-ssh-manager), or download the desktop application below; the 4.x application updates itself.