src/docs/SSH_SHARING.md
croc ssh creates one persistent, collaborative terminal without exposing the
host's normal SSH service. It is intended for pair debugging, support sessions,
demos, and incident response where participants should be able to join with a
short-lived invitation and no prearranged account.
Start an attached session on Linux, macOS, FreeBSD, or OpenBSD:
croc ssh
The host prints two independently generated invitations:
Read/write: CROC_SECRET='...' croc ssh
Read-only: CROC_SECRET='...' croc ssh
Browser read/write: https://getcroc.com/#ssh?code=...
Browser read-only: https://getcroc.com/#ssh?code=...
Both roles see the same PTY and its existing transcript. Read/write clients and the attached host may type. Input from a read-only client is consumed without being sent to the PTY. Any number of clients may attach, subject to the selected croc relay and the host's resources.
Useful host options:
croc ssh --headless
croc ssh --duration 30m
croc ssh --dir /path/to/project
--headless keeps the session available without attaching the host terminal.
The default lifetime is 12 hours. Ctrl-] detaches an attached host or guest
while leaving the shared shell alive; Ctrl-C in the hosting process stops the
session. A command typed inside the shared terminal, including exit, affects
the one shared shell and therefore ends the session for everyone.
For guests, Ctrl-C remains normal terminal input and interrupts the foreground
program in the shared shell. Only the locally attached hosting process treats
it as the stop-session shortcut.
Global --relay and --pass options select a self-hosted croc relay. The host
includes non-default relay settings in both printed join commands, so guests do
not have to reconstruct them separately.
On Unix, pass invitations in CROC_SECRET or paste one at the prompt so it does
not appear in the process list. Classic mode permits croc ssh word-..., and
Windows accepts that spelling directly. Hosting requires a PTY-capable platform
(Linux, macOS, FreeBSD, or OpenBSD). Those platforms and Windows can join.
Relay-only builds and other targets report that SSH sharing is unsupported.
The croc-web homepage has a top-level Files / SSH switch. SSH mode accepts
either printed invitation, reports the authenticated role, and opens the shared
PTY in an xterm.js terminal. Ctrl-C is sent to the shared shell normally;
Disconnect or Ctrl-] leaves the session. Browser input is disabled for a
read-only invitation in addition to the host-side enforcement.
The browser is join-only. It uses the ordinary croc relay path rather than
Tailcat, cannot host a terminal, and cannot connect to an arbitrary SSH server.
For self-hosting, croc-web must use the same ordered relay pool and relay
password as the croc ssh host. Role-specific browser links carry the
invitation after the URL's #, so it is not sent to the web server. The page
reads the fragment, removes the invitation from the address bar, and keeps it
in memory only while it is needed for the initial connection or reconnection.
It is not placed in browser storage, logs, error reports, or analytics events.
SSH clients include a fixed ssh-rendezvous-v1 feature in the unencrypted PAKE
envelope. An analytics-enabled relay may use only that marker to emit an
aggregate ssh-rendezvous event, and an analytics-enabled web client emits
ssh-browser-session after its SSH handshake succeeds. Neither event includes
the invitation, derived room, access role, relay settings, commands, or
terminal data.
After a participant has attached successfully, a transport failure causes the client to run PAKE again, authorize a fresh transport, verify the SSH host key again, and reattach to the same PTY. The default retry window is two minutes:
croc ssh --reconnect-window 10m
croc ssh --no-reconnect
Transport selection defaults to auto: try Tailcat (direct UDP with DERP
fallback), then reauthenticate over the ordinary croc relay if Tailcat cannot
establish the SSH connection. It can be constrained for diagnostics or network
policy:
croc ssh --transport tailcat
croc ssh --transport relay
There is only one local-terminal reader across reconnects, so an abandoned SSH copy loop cannot steal input from the replacement connection. Up to 8 MiB of PTY output is retained in memory and replayed on attachment. If older output was trimmed, the client receives a warning and a terminal reset before the retained transcript.
The browser follows the same two-minute reconnect policy over the relay. It reruns PAKE, revalidates the role and pinned host key, and resets its terminal emulator before the host replays retained output.
Each invitation contains six EFF words. The first two derive an opaque relay room identifier; the remaining four are the PAKE secret. The host and guest run purpose- and room-bound PAKE with mutual key confirmation over a normal croc relay room. Relay keepalives are transport frames and are ignored by this exchange.
After PAKE, the guest sends a fresh Tailcat node public key and a random, single-use SSH client credential through the encrypted control channel. The host returns an authenticated offer containing:
For the primary path, the host allows that Tailcat key and binds its deterministic tunnel source address to exactly one role. Read/write and read-only traffic use separate filtered ports, and the role is checked again at connection dispatch. The embedded SSH server also requires the single-use credential before attaching either transport to the terminal. Both grants are short-lived and consumed by one SSH connection. Reconnection therefore cannot bypass PAKE.
Tailcat supplies an accountless userspace WireGuard network. It begins through
a DERP server and upgrades to a direct UDP path when NAT traversal succeeds. If
Tailcat itself cannot establish the SSH connection, the guest performs a fresh
PAKE exchange requesting relay; after the authenticated offer, both sides
switch that ordinary croc relay room from framed control messages to the raw,
pinned SSH stream. This fallback works through the same croc relay selected by
the invitation and is identified in the guest's connection message.
The embedded SSH layer provides terminal channel semantics and an independently encrypted transport on both paths. No host SSH configuration, user password, authorized key, Tailscale account, or inbound firewall rule is needed.
The embedded SSH service accepts only an interactive PTY session. It does not accept remote commands, extra channel types, subsystems such as SFTP, agent or port forwarding, or arbitrary destination ports.
croc ssh. The feature does not add a sandbox, container, audit log, or command
policy. Use a suitably constrained account or environment when that matters.