Repository navigation
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 setupandssh-manager codex status.setuppoints
OpenAI Codex at this engine: only thessh-managertable of
~/.codex/config.tomlis 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.statussays what Codex runs today. They were
documented (withmigrate,testandconvert, which have no meaning with the
V4 engine;ssh-manager export servers.tomlwrites the servers as TOML) and none
of them existed. The entry thatConfigLoader.saveToCodexConfigwrote 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 azhlocale, 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_executewithbackground: truestarts 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 withssh_process_manager.
The log file is never chosen by the caller: a free path would let a command
that passes thereadonlypolicy overwrite any file the SSH user can write,
so the engine creates a private one withmktemp. 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,sftpand a remotersyncrun 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.installedits only its own
entry in the agent's user settings file (a copy is kept the first time),remove
restores the file,printandstatuswrite 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=falsekeeps 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_LOGset, 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_managerand the rest now
leave a line withsuccess, anderrorwhen 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 setisError, 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_dumpandssh_backup_createanswered success with an empty archive of
20 bytes. The cron script made byssh_backup_scheduledid 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 hasset -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_importorssh_backup_restorestart the database program (MySQL and
PostgreSQL). A plain dump under a.gzname, which a backup made without
compression used to leave, is still restored. - A MongoDB backup can be restored. It was written as
<id>.tar.gzbut
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_dumpreports the path it really wrote. ssh-manager server remove prodremovesprod, notprod_eutoo. The CLI
matched a server's lines by the bare prefixSSH_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 addwrote
a#b c"dbare, and the engine read it back asa(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 everyoverlaylayer repeated the same usage, andtmpfs,devtmpfsand
squashfsmounts 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_createwithtype: "remote",ssh -R) never
worked: they failed withthis.ssh.forwardIn is not a function, because the
connection wrapper only hadforwardOut. 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
MaxStartupsor 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.dbPasswordanddb_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.