Pure Rust Mosh client — cross-platform, wire-compatible, no Cygwin
Built for Netcatty · usable anywhere
MoshCatty is a pure Rust implementation of a Mosh client.
It speaks the real Mosh wire protocol (AES-128-OCB3, SSP, fragments, HostBytes paint) against stock mosh-server on Linux/macOS/Windows — without Cygwin, terminfo databases, or DLL bags.
It is the default path Netcatty uses to ship a reliable multi-platform Mosh client (especially on Windows, where classical Cygwin glue breaks).
SSH bootstrap (Netcatty / your tool)
│
▼ MOSH CONNECT <port> <key>
┌────────────────────┐
│ mosh-client │ ← MoshCatty binary
│ pure Rust · UDP │
└─────────┬──────────┘
│ AES-OCB · SSP · HostBytes
▼
remote mosh-server (stock)
| Approach | Windows reality |
|---|---|
Cygwin mosh-client + partial DLLs |
Cursor-only / terminfo / PTY sandwich failures |
| FluentTerminal old PE | Stale provenance, encoding issues |
| MoshCatty | One binary (Windows CRT static-linked), same protocol stack everywhere |
Peer products either skip Windows Mosh or own a private engine. MoshCatty is the open, Netcatty-aligned engine.
- Wire-compatible with stock
mosh-server1.4.x (protocol v2) - Cross-platform: Linux / macOS / Windows (no Cygwin runtime)
- Drop-in CLI:
MOSH_KEY=<key> mosh-client <host> <port> - Speculative local echo (stock overlay rules on a mosh-go Diff path):
host_fb → Confirm → Overlay → Diff (
MOSH_PREDICTION_DISPLAY=adaptive|always|never|experimental). Backspace always waits for the authoritative host frame so rapid repeats cannot speculatively erase shell prompt cells. Seedocs/prediction.md. - Recoverable-outage status: stock-style connecting / last-contact / last-reply notification bar, cleared automatically after the path recovers
- Local escape command:
Ctrl-^ .quits andCtrl-^ ^sends a literalCtrl-^, withMOSH_ESCAPE_KEYconfiguration - Library API (
moshcattycrate) for embedders - RFC 7253 AES-128-OCB3 + mosh-go interop vectors in CI
- Netcatty-ready: fits existing SSH bootstrap + node-pty swap
Download the archive for your platform from the latest release:
| Asset | Platform |
|---|---|
mosh-client-linux-x64.tar.gz |
Linux x86_64 |
mosh-client-linux-arm64.tar.gz |
Linux aarch64 |
mosh-client-darwin-universal.tar.gz |
macOS universal |
mosh-client-win32-x64.tar.gz |
Windows x64 |
# Example: Linux x64
tar -xzf mosh-client-linux-x64.tar.gz
chmod +x mosh-client
MOSH_KEY=... ./mosh-client 203.0.113.10 60001git clone https://github.com/binaricat/MoshCatty.git
cd MoshCatty
cargo build --release
# binary: target/release/mosh-clientRequires Rust 1.75+.
MoshCatty is the network client only. You (or Netcatty) still run SSH to start mosh-server and obtain:
MOSH CONNECT <udp-port> <base64-key>
Then:
export MOSH_KEY='xxxxxxxxxxxxxxxxxxxxxx'
mosh-client 192.0.2.10 60001| Variable | Meaning |
|---|---|
MOSH_KEY |
Required. Session key from MOSH CONNECT |
MOSH_PREDICTION_DISPLAY |
adaptive (default), always, never, or experimental |
MOSH_PREDICTION_OVERWRITE |
yes to predict overwrite-mode input |
MOSH_ESCAPE_KEY |
One ASCII byte for the local command prefix; empty disables it (default Ctrl-^) |
MOSH_NO_TERM_INIT |
Skip local alternate-screen setup (Netcatty sets this) |
COLUMNS / LINES |
Initial / fallback terminal size |
| (Unix) live winsize | Polled via TIOCGWINSZ |
| (Windows) console size | Polled via GetConsoleScreenBufferInfo |
Like stock mosh-client, the session runs on the local alternate screen:
- start:
\e[?1049h\e[?1h(smcup + application cursor keys) - exit: reset cursor/mouse modes +
\e[?1049l(rmcup)
That keeps HostBytes paint off the primary buffer and restores the pre-mosh
screen when the client exits. Set MOSH_NO_TERM_INIT=1 to skip (same env as
upstream mosh).
Note: remote apps (vim/htop) still rely on mosh-server's framebuffer. Stock mosh-server does not implement DEC 1049 dual buffers, so residual cells after quitting a full-screen remote app can still appear — that is an upstream server limitation, not fixed solely by client smcup/rmcup.
Under ConPTY, Ctrl+C raises a Windows CTRL_C_EVENT in addition to the \x03
byte on stdin. MoshCatty installs a console control handler that ignores
CTRL_C / CTRL_BREAK as process-kill signals, and clears cooked console input
flags analogous to Unix cfmakeraw (ISIG off). Result: Ctrl+C interrupts the
remote shell instead of exiting the client with STATUS_CONTROL_C_EXIT.
MoshCatty also enables virtual-terminal input before reading stdin. This keeps the escape sequences that ConPTY receives from terminal hosts intact, including arrow keys, Ctrl/Alt-modified arrows, and Alt shortcuts.
The local Mosh command prefix works under ConPTY as well: Ctrl-^ . closes the
Mosh session, and Ctrl-^ ^ sends a literal Ctrl-^ to the remote program.
MoshCatty deliberately does not implement stock Mosh's Unix job-control suspend
command because suspending a child ConPTY can also stall its host application;
that local command is consumed and is never forwarded as Ctrl-Z to the remote
shell.
Release builds on x86_64-pc-windows-msvc / aarch64-pc-windows-msvc use
static CRT linking (.cargo/config.toml → -C target-feature=+crt-static).
That embeds the C runtime into mosh-client.exe, so the PE does not import:
VCRUNTIME140.dllMSVCP140.dllucrtbase.dll/api-ms-win-crt-*.dll
Users without the Visual C++ Redistributable can still run the binary. Remaining
imports are always-present OS modules (kernel32, ntdll, ws2_32, …).
# local Windows verify after cargo build --release
dumpbin /dependents target/release/mosh-client.exe
# should not list VCRUNTIME140 or api-ms-win-crt-*Release Linux binaries are built on the same distro baseline Netcatty packages
against, not on bare ubuntu-latest (which links a newer glibc and fails to
start on older supported installs):
| Target | Build image | Max required GLIBC |
|---|---|---|
linux-x64 |
almalinux:8 |
2.28 |
linux-arm64 |
debian:bullseye |
2.31 |
CI enforces the floor with scripts/assert-max-glibc.sh after cargo build --release. Do not regress to host Ubuntu runners for release Linux jobs.
# after a Linux release build
bash scripts/assert-max-glibc.sh target/release/mosh-client 2.28 # x64
bash scripts/assert-max-glibc.sh target/release/mosh-client 2.31 # arm64crypto ──► fragment ──► pb ──► transport (SSP) ──► terminal ──► client
OCB3 1214B TI/Host/User zlib+ack HostBytes UDP session
| Module | Role |
|---|---|
crypto |
AES-128-OCB3, mosh datagram seal/open |
fragment |
Instruction fragmentation (upstream layout) |
pb |
Hand-rolled protobuf codecs (field-compatible) |
transport |
SSP state numbers, throwaway, RTO, OOO crypto seq |
terminal |
Apply HostBytes paint stream |
client |
Dial, keys, resize, keepalive, network death |
cargo test
cargo build --release
cargo clippy --all-targets -- -D warnings # when availableTests include RFC 7253 empty-AAD vectors, mosh-go interop vectors, fragment OOO reassembly, SSP bidirectional rounds, and a fake-server client path.
CI builds multi-platform mosh-client archives and publishes GitHub Releases with tags:
moshcatty-0.1.4
Netcatty pulls those assets the same way it previously used Netcatty-mosh-bin:
MOSH_BIN_OWNER=binaricat
MOSH_BIN_REPO=MoshCatty
MOSH_BIN_RELEASE=moshcatty-0.1.4 # or resolve latest moshcatty-*
npm run fetch:mosh
Artifact names stay Netcatty-compatible:
mosh-client-linux-x64.tar.gz
mosh-client-linux-arm64.tar.gz
mosh-client-darwin-universal.tar.gz
mosh-client-win32-x64.tar.gz
SHA256SUMS
GPL-3.0-or-later — same family as Mosh and Netcatty.
Protocol references: Mosh paper / upstream field numbers; interop vectors from mosh-go (MIT). No AGPL code is vendored.
Made with 🐱 for the Netcatty ecosystem
