koh 0.9.0

koh — a resilient peer-to-peer remote shell: mosh, rewritten in Rust over iroh
Documentation

koh — mosh, rewritten in Rust over iroh

koh is a from-scratch Rust reimplementation of mosh (the mobile shell) whose transport is iroh peer-to-peer QUIC instead of mosh's UDP/OCB. You get mosh's signature feel — instant local echo on laggy links, survival across suspend/resume and IP changes, no head-of-line blocking — while iroh handles encryption, NAT traversal, relay fallback, connection migration, and RTT. It's one small binary that gives you a real remote shell by endpoint id, with no listening port and no SSH.

The name nods to iroh via Avatar: Koh the Face Stealer takes you the instant you show a past expression — survival means showing only your current face. That's the protocol exactly: only the latest screen state is authoritative; every superseded state is collapsed and discarded. koh is a state-synchronization system, not a tunnel — if the screen changed 100 times in 40 ms, only the final state is sent.

Features

  • Instant local echo — a predictor shows your keystrokes immediately (underlined on high-RTT links), then confirms/corrects from the server. Password prompts get no predicted echo automatically.
  • Detachable, reattachable sessions — close the lid, reconnect, your shell is right where you left it (keyed by your client id; no tmux needed for survival).
  • Transparent auto-reconnect — the client rides out brief outages on the same connection and re-dials across IP changes / long screen-offs, holding the last screen under a reconnecting… banner. It never drops you back to a local prompt.
  • No head-of-line blocking — a burst of superseded output never delays the current frame; you always re-sync to the screen as it is now, never a replayed backlog.
  • Interactive apps work — terminal-reply synthesis (DSR/DA/DECRQM) so vim/htop/fzf behave; exit-status propagation so the client exits with the remote shell's code.
  • Secure by construction — no listening port (reachable only by a non-enumerable node-id on an allowlist), peer authenticated by the QUIC/TLS handshake with no TOFU, and an identity key that is always encrypted at rest. forbid(unsafe), panic-free by lint.
  • Tiny & portable — one ~11 k-line crate, ~25 deps, runs on Linux/macOS and Android/Termux.

Install

cargo install koh                                               # from crates.io
# or, latest from git:  cargo install --git https://github.com/gold-silver-copper/koh
# or, from a checkout:  cargo build --release

Platforms: Linux and macOS (x86_64 and aarch64), and Android via Termux. Windows is not supported (koh uses Unix PTYs + file-permission primitives) — use WSL2. koh is binary-first: the library crate is published only so the in-tree tests can drive it, and its API is internal and unstable — depend on the koh binary, not the crate API.

Quickstart

koh authorizes by endpoint id (your machine's public key) — there are no passwords or accounts.

# 1. On the client, print its id (creates an encrypted identity key on first run):
koh id
#   3f9c…(64 hex)

# 2. On the server, allow that id and start (prints its own id + a scannable QR):
koh serve --allow 3f9c…

# 3. On the client, dial the server's id:
koh connect 871b…
#   connected. (Ctrl-^ then . to disconnect)

By default the bare id is dialed via n0's public relay + DNS discovery (works across NATs). For LAN or a self-hosted relay:

koh serve --local --allow 3f9c…           # no relay; prints its UDP port
koh connect 871b… --direct 192.168.1.5:41234

koh serve  --relay-url https://relay.example:3340 --allow 3f9c…
koh connect 871b… --relay-url https://relay.example:3340

Commands: koh serve (host a shell), koh connect <id> (attach), koh id (print your id), koh key passwd|info (manage the identity key's passphrase). Useful flags: --clipboard (opt-in OSC-52), --session-ttl-secs, --max-connections/--max-sessions. koh keeps its keys under ~/.config/koh/ (override a path with --key-file). Local-echo prediction is always on (no flag).

Android / Termux works out of the box; koh pins a public DNS resolver to sidestep an Android JNI-context panic (override with KOH_DNS=1.1.1.1). Run termux-wake-lock so the OS doesn't freeze the process during a long screen-off.

Alternatives

koh's bet is a combination no single alternative has: mosh-style predictive echo + detach/reattach

  • p2p with no listening port + memory-safe Rust. The honest landscape (✅ built-in · ❌ no · ⚠️ partial):
koh mosh OpenSSH Eternal Terminal wush
Transport / reach p2p QUIC, by node-id UDP, over an SSH login TCP :22 TCP :2022, over SSH p2p WireGuard / DERP
No listening port ✅ ❌ (needs sshd) ❌ ❌ (needs sshd) ✅
Predictive local echo ✅ ✅ ❌ ❌ ❌
Reconnect / roam (survive IP change) ✅ ✅ ❌ ✅ ⚠️ (WireGuard)
Persistent session (detach → reattach) ✅ ❌ ❌ (use tmux) ✅ ❌
Scrollback ❌ (screen-sync only) ❌ ✅ ✅ ✅
File transfer ❌ ❌ ✅ (scp/sftp) ❌ ✅ (wush cp)
Port forwarding ❌ ❌ ✅ ✅ ❌
Auth node-id allowlist, no TOFU via SSH keys / 2FA / FIDO2, TOFU host via SSH shared overlay key
Multi-user / accounts ❌ (single-operator) ✅ (SSH) ✅ ✅ (SSH) ❌
Language Rust, forbid(unsafe) C++ C C++ Go
Maturity new (2026) mature, ubiquitous universal established new (2024)
  • mosh — the predictive-echo + roaming ancestor koh descends from. koh adds detach/reattach, forward-secret per-session crypto, and p2p with no SSH dependency. Still pick mosh for a battle-tested tool that's packaged everywhere and rides your existing SSH accounts/2FA.
  • OpenSSH — the universal everything-tool: forwarding, sftp, agent, jump hosts, multi-user. koh isn't a replacement — it's a focused interactive shell that's safer by construction (no port, no TOFU, Rust). ssh's edges koh lacks: post-quantum-default KEX, FIDO2 hardware keys, privilege separation, and decades of audit. Still pick ssh for file transfer, tunnels, accounts, or scripting.
  • Eternal Terminal — reliable auto-reconnect + native scrollback + port-forwarding over an SSH-bootstrapped TCP link. koh adds predictive echo and p2p (no port, no SSH) but lacks ET's scrollback. Still pick ET if you live in scrollback, must cross a strict TCP-only firewall, or want SSH-native auth.
  • wush — koh's closest modern-p2p cousin (WireGuard/DERP) and the file-transfer king (wush cp). koh adds the mosh-feel (predictive echo, detach/reattach) and drops the Tailscale/WireGuard dependency, but has no file transfer yet. Still pick wush for one-command p2p file transfer.

Also at the edges: tmux/zellij (multiplexing), sshx/upterm (terminal sharing), Tailscale SSH / Teleport (managed access). koh's lane is the mobile-first, predictive, p2p interactive shell — and its biggest gaps vs the field are file transfer and scrollback.

Why koh doesn't ride on SSH (the way mosh does)

mosh isn't a standalone protocol — it bootstraps over an SSH login. mosh user@host runs ssh first, which authenticates you, drops you into your account, and launches mosh-server; mosh then takes over the interactive session over UDP. So mosh inherits SSH's auth, accounts, host keys, bastions, agent, and PAM/2FA for free — it only had to solve the responsive-roaming-session piece.

koh deliberately doesn't, because every koh property depends on not needing SSH:

  • No listening port. Riding on SSH means a reachable sshd — an open TCP port plus its whole codebase in the trust path. koh is reachable only by a non-enumerable node-id through relays / hole-punching, including a machine with no open ports at all (your phone → your NAT'd PC). That's the whole p2p/mobile premise.
  • No TOFU, no second protocol, forward secrecy. koh's node-id is the identity, authenticated by the QUIC/TLS handshake on every connect — no known_hosts first-use window, and no static session key piggybacked over a second protocol (mosh's MOSH_KEY). koh's own channel is already stronger, so there's nothing to gain by bootstrapping over SSH.

The honest trade: by solving auth itself — cryptographically, via the node-id allowlist — koh gives up what SSH handed mosh for free: user accounts / multi-user, SSH's auth factors (passwords, 2FA, FIDO2, PAM) and policy (ForceCommand, authorized_keys options), and the universal sshd install base. That is why koh is a single-operator tool for machines you own, not an ssh replacement. (A future opt-in "koh over an existing SSH connection" mode could inherit SSH's auth while keeping koh's predictive-echo + detach/reattach session — but it would re-add the open port and a second protocol, so it's deliberately out of scope today.)

Security at a glance

  • Authorization is a node-id allowlist — the sole gate. At least one --allow <id> is required; there is no "accept any peer" mode and no passphrase/PAKE second factor (the node-id is cryptographically authenticated by the handshake, with no trust-on-first-use).
  • Every authorized peer is still untrusted on the data plane: resize clamps before any vt100 allocation, inflation/replay/reassembly/accumulation caps, bounded handshake + admission timeouts, and vt100 catch_unwind on both sides.
  • The identity key is always encrypted at rest (Argon2id + AES-256-GCM, ≥12-char passphrase floor, 0600 / O_NOFOLLOW, zeroized); koh keeps every file it owns under ~/.config/koh.

See SECURITY.md to report a vulnerability and docs/THREAT_MODEL.md for the full model. No external audit yet — calibrate trust accordingly.

Non-goals

No wire compatibility with upstream mosh; no multiplexing (one session, one shell — use tmux for windows/panes); no scrollback sync (only the visible screen, like mosh); no port/agent/X11 forwarding, sftp, or multi-user model. OSC-52 clipboard write is supported but off by default.

Status & roadmap

Feature-complete against mosh's core (detachable sessions, terminal-reply synthesis, exit-status propagation, the predictor, OSC-52). Hundreds of tests pass across the tiers, including end-to-end over real iroh and a full Android-emulator suite. This is the transport+terminal core for a planned Bevy-based Android terminal; next up is two-device real-network acceptance and a Bevy front-end reusing the terminal + input + predict + transport_iroh modules directly.

Docs

License

GPL-3.0-or-later (matching upstream mosh).