Security model

Your terminal is your secrets. We treat it that way.

Agent sessions carry API keys, source code, file paths and passwords. Gallager encrypts everything sensitive on-device, so the relay — ours or yours — only ever forwards ciphertext.

// pairing — happens once
Mac ⇄ iPhone  exchange X25519 public keys via short pairing code
shared secret = ECDH(privateKey, peerPublicKey)
sessionKey    = HKDF-SHA256(sharedSecret, salt: "ClaudeSpy-E2EE-v1")
// every message after that
ciphertext = ChaCha20-Poly1305.seal(payload, sessionKey)
Mac → relay → iPhone  relay sees: ciphertext, nothing else

Private keys never leave your devices

Long-term X25519 key pairs are generated on first launch and stored in your Keychain. The relay stores device IDs, public keys and push tokens — never your private keys.

What gets encrypted

Terminal streams, keystrokes, hook events (tool inputs and outputs, project paths, session IDs), command responses and session state. Basically everything with content in it.

What the relay can see

Connection state, message timing, payload sizes, and the pairing metadata it needs to route: device IDs and names, public keys, push tokens. Enough to route messages between two paired devices, not enough to know what you're building.

Compromising the relay yields nothing

Session keys are derived on-device and are never sent anywhere. Breaching the relay — or recording its traffic — yields ciphertext, not your terminal.

No accounts, no cloud storage

No user database and none of your content: the relay keeps only pairing records (device IDs, public keys, push tokens) — plus subscription state on the hosted plan. Your terminal content is never stored or logged, anywhere.

Don't take our word for it.
The E2EE core is about five hundred lines of CryptoKit. Read it, audit it, or run your own relay so the question never comes up. Source on GitHub →