Security and encryption
codeout encrypts the link between each device and the daemon with libsodium. The transport underneath is treated as untrusted. Whether it is your LAN, a VPN, or a public tunnel, only the two endpoints can read the traffic, and tampering is detected rather than merely hoped against.
The threat model
Assume the transport is hostile. A passive listener on the wire, an active tamperer who can modify bytes in flight, and a malicious tunnel or proxy that handles every packet are all assumed to be present. None of them should learn the contents of a session or be able to forge, replay, or reorder traffic. The trust boundary is your daemon host and your paired devices; protect those two and the rest of the path can be as untrustworthy as it likes.
Session keys: crypto_kx
At pairing and at every reconnect, the device and daemon run a libsodium crypto_kx key exchange. Each side derives a pair of symmetric session keys from its own keypair and the other side's public key. The long-term secrets never travel across the wire. An eavesdropper learns public keys and nothing usable, because public keys are, by design, the part you are allowed to see.
Two streams: secretstream
Once both sides share session keys, traffic flows through libsodium's crypto_secretstream using XChaCha20-Poly1305. codeout runs two streams, one per direction: device to daemon, and daemon to device. Splitting the directions means the two halves of the conversation never share keystream and cannot be crossed or confused for one another.
Every message is encrypted and authenticated, so tampering is detected, not just eavesdropping. Each stream is ordered and chained, so messages within it cannot be silently dropped, reordered, or replayed. If a byte is flipped or a frame is missing, the receiving side knows and tears the stream down rather than carrying on with corrupted state.
device to daemon
Your keystrokes and control messages, sealed and authenticated in one secretstream.
daemon to device
The terminal output and session events, sealed and authenticated in the other.
Pairing: QR and manual
Pairing bootstraps that first key exchange. The QR carries the daemon address and a single-use, short-lived pairing secret; the typeable code carries the same secret in a form a human can read aloud. Because the secret is one-time and expiring, an old photo of a QR is useless and a code cannot be reused to pair a second, unwanted device. After the exchange, the app shows the daemon fingerprint so you can confirm you reached the right machine. The QR verifies that fingerprint automatically; the manual flow makes the check visible. Both paths are covered in detail on the Pairing a device page.
Per-device tokens
Pairing leaves each device with its own 32-byte token. The token authorizes exactly one device, so you can see your devices individually and revoke them individually. Revoking a token cuts off one device and touches no other. There is no shared password that, once leaked, exposes everything: every device carries its own credential, and losing one costs you exactly that one.
The daemon challenge
Holding a token is not enough to connect. On every connection the daemon issues a fresh random challenge. The device must answer it using its session keys before the daemon will accept a single byte of session traffic. Because the challenge is new each time, an answer that was valid a moment ago is meaningless the next time around.
Why replay fails
A recording of an old, valid handshake proves nothing when replayed later, because it answers last time's challenge, not this time's. This defeats whole-stream replay: an attacker who captured an entire session's ciphertext cannot re-send it to impersonate a device or re-run a command, because the daemon would hand the replay a new challenge it has no way to answer. Inside a live session, the chained secretstream already blocks replay of individual frames; the per-connection challenge extends that protection to the connection as a whole.
At a glance
crypto_kx, per-direction session keys, long-term secrets never sentcrypto_secretstream XChaCha20-Poly1305 streams, one per direction, authenticated and chainedThe transport is assumed hostile. A passive listener, an active tamperer, and a malicious tunnel or proxy all see only authenticated ciphertext. The trust boundary is your daemon host and your paired devices. Protect those two and you have protected the whole system.