How an end-to-end encrypted terminal relay works
To reach a Mac behind a home router without opening ports, something on the public internet has to pass traffic between the Mac and the phone. That something is a relay. The interesting question is how to make the relay useless to an attacker, including us.
Why a relay at all
Home routers don't accept incoming connections unless you set up port forwarding, and many mobile and home networks put you behind carrier-grade NAT, where you can't even do that. Outgoing connections work everywhere. So both sides connect out to a relay with a public address, and the relay joins the two connections.
That makes the relay a perfect place to read or tamper with everything. Unless everything it carries is encrypted with keys it never has.
The goal
- The relay can't read commands, output, session names or pairing codes.
- The relay can't pretend to be your Mac to your phone, or your phone to your Mac.
- A recorded connection can't be replayed later, and a changed message is detected.
- No home-made cryptography: every primitive comes from libsodium, used the same way on both ends (the C library on the Mac, its WebAssembly build in the phone's browser).
Step 1: identities
Your Mac makes a long-term Ed25519 key pair for signing, and an X25519 key pair for receiving sealed messages. Each phone makes its own Ed25519 key pair when you pair it, kept in the browser's storage. Only public keys ever leave a device.
Step 2: pairing through an untrusted relay
Pairing is where a relay could most easily cheat: if it could swap the phone's public key for its own during pairing, it could later impersonate the phone. The QR code on your Mac prevents that. It carries:
- your Mac's public signing key and sealing key, and
- a one-time code that expires in ten minutes.
Your phone reads these straight from the screen, not over the network. It then sends {code, name, its public key} as a sealed box (crypto_box_seal) to your Mac's sealing key. The relay passes along a blob it can't open, so it can't read the code, and it can't replace the phone's key without the code. Your Mac checks the code, remembers the phone's key, and replies with a signature over the new device ID and that key. The phone verifies the signature with the Mac's key from the QR code, so a fake reply is rejected too.
Step 3: a handshake for every connection
Each time the phone connects, both sides prove who they are, just for this connection:
- The phone sends its device ID and a fresh one-time key (
crypto_kx). - The Mac replies with its own fresh one-time key, signed with its long-term key over both one-time keys.
- The phone checks that signature against the Mac key it saved at pairing, then signs both one-time keys with its own long-term key.
Because each signature covers both fresh keys, it's only valid for this exact connection: a recording of yesterday's handshake proves nothing today. Session keys come from the one-time keys, so each connection has its own.
Step 4: the message stream
Terminal traffic then flows as crypto_secretstream_xchacha20poly1305, one stream in each direction. Every message is encrypted and authenticated, and the stream is ordered: a message that has been changed, dropped, reordered or replayed fails to decrypt, and the connection ends. The relay sees a series of opaque blobs of various sizes.
What's left visible
End-to-end encryption hides content, not the fact that you're talking. The relay can see which Macs are online (as public keys), the IP addresses of connected phones, and how much data flows and when. It can also refuse to carry traffic, which would make remote access fail but can't expose anything. PandaSend's relay keeps no request logs and stores nothing about sessions, but the design doesn't depend on trusting that.
Checking it, not assuming it
Our automated tests run a real session through a real relay, record every byte it carries, and check that the command, its output, the session name and the pairing code appear nowhere. Other tests try an impostor phone key, a fake Mac, a tampered message and a revoked phone, and expect each to be refused.
Tests only check what we thought to test. The riskiest part of any design like this is how the pieces fit together, so we keep reviewing it, and we welcome more eyes on it. If you spot something we missed, please tell us.