How AFK Works
Technical architecture of real-time encrypted terminal streaming.
System Overview
AFK has three components: the AFK CLI captures your terminal on the desktop, the Backend routes messages between devices, and the Client displays the terminal on your phone. The backend is a blind relay — it routes encrypted bytes it cannot read.
Session Streaming
How terminal data flows in real-time between your desktop and mobile device.
1 PTY Capture
The AFK CLI spawns a pseudo-terminal around your shell (or Claude Code). It captures output in 1KB chunks and maintains a 1MB scrollback buffer so clients can catch up on reconnect without missing any output.
2 Binary Protocol
Messages use MessagePack over WebSocket — a binary serialization format
that's 33% smaller than JSON. Connections set TCP_NODELAY to disable Nagle's algorithm
for the lowest possible latency on each keystroke.
3 Message Routing
The backend is message-type-agnostic — it reads the envelope header
(connection_id) to determine the routing target and forwards the encrypted payload as an opaque blob.
New message types work automatically without backend changes.
Each connected client has its own output stream and reads at its own pace.
4 Bidirectional I/O
Input flows back from the mobile app to the terminal: Client → AFK Backend → AFK CLI → PTY stdin. The same E2E encryption protects input, so the backend cannot see what you type.
5 Reconnection
On reconnect, the AFK CLI replays its scrollback buffer so the client instantly sees the full terminal state. Warm reconnect detection avoids redundant replays, and exponential backoff prevents reconnect storms during transient network issues.
End-to-End Encryption
The zero-knowledge encryption layer between the AFK CLI and client. The backend relays encrypted bytes without the ability to decrypt them.
Key Exchange
Both sides generate ephemeral X25519 keypairs and exchange public keys via the backend. The backend transports the public keys but cannot derive the shared secret — that requires a private key it never sees.
# AFK CLI side
cli_sk = X25519.generate() # private, never leaves device
cli_pk = cli_sk.public_key() # sent to client via backend
# Client side
client_sk = X25519.generate() # private, never leaves device
client_pk = client_sk.public_key() # sent to AFK CLI via backend
# Both compute the same shared secret independently
shared = ECDH(my_private, their_public) # identical on both sides
Encryption
Terminal data is encrypted with AES-256-GCM using the shared secret. Nonces use a counter-based scheme (not random), eliminating the risk of nonce reuse even across millions of messages. Each message carries a 16-byte authentication tag that prevents tampering.
# Encrypt (AFK CLI or client)
nonce = counter_to_nonce(msg_counter++) # deterministic, no reuse
ciphertext = AES-256-GCM.encrypt(
key = shared_secret,
nonce = nonce,
plaintext = terminal_data
) # includes 16-byte auth tag
# Decrypt (other side)
plaintext = AES-256-GCM.decrypt(
key = shared_secret,
nonce = nonce,
ciphertext = ciphertext
) # fails if any byte was altered
What the Backend Sees
The backend is message-type-agnostic. It reads only the envelope header for routing and never parses message payloads. New message types are forwarded automatically without any backend changes.
Visible to Backend (envelope header only)
- user_id, session_id
- connection_id (routing target)
- Message type tag (for logging, not routing)
- Encrypted payload bytes (opaque blob)
- Message size
Invisible to Backend
- Terminal output text
- Keyboard input
- File contents
- Commands executed
- Any payload content
Context Isolation
A second, independent encryption layer on the backend. While E2EE protects against a compromised server, context isolation protects against routing bugs in the backend code itself.
Why This Exists
Even if a backend bug routes session A's data to user B, user B cannot decrypt it. The data is encrypted with user A's context key, which user B doesn't have. The failure mode is garbled data, not a data leak.
Key Hierarchy
Context isolation uses a three-level key hierarchy:
Master Key
A single key stored outside the database (environment variable). Encrypts all per-user keys. Acts as a Key Encryption Key (KEK).
Per-User Keys
Randomly generated per user, encrypted with the master key (AES-256-GCM), stored in PostgreSQL, cached in an LRU memory cache.
Per-Session Keys
Derived as HMAC-SHA256(user_key, session_id) — unique per session,
deterministic, never stored. Recomputed on demand.
Cipher Choice
Context isolation uses ChaCha20 (stream cipher, no authentication tag). An auth tag is unnecessary here because the E2EE layer already provides authenticated encryption — if anyone tampers with the data, GCM decryption on the client will fail. ChaCha20 is fast, constant-time, and avoids the overhead of a second authentication tag.
Double Encryption Flow
The step-by-step journey of terminal output through both encryption layers.
AFK CLI encrypts with E2EE
Terminal output is encrypted with the shared secret (AES-256-GCM). Only the client can decrypt.
Backend adds context encryption
The already-encrypted data is encrypted again with the user's session key (ChaCha20) before storage.
Backend strips context encryption
When delivering to the client, the backend decrypts its own layer, restoring the E2EE ciphertext.
Client decrypts E2EE
The client decrypts with the shared secret, recovering the original terminal output. If any byte was tampered with, GCM authentication fails.
Edge Network & Multi-Region
How Cloudflare and multi-region deployment minimize latency for terminal streaming.
Cloudflare Edge
All connections hit Cloudflare's global network first. SSL terminates at the nearest Point of Presence, so the TLS handshake is fast regardless of origin location. Cloudflare also provides DDoS protection, WAF rate limiting, and auto-compression.
Geo-Steering
Cloudflare routes each connection to the nearest region automatically. A developer in the US connects to SFO, Europe to FRA, Asia to BLR. No client-side configuration required.
Region-Aware Sessions
When the AFK CLI creates a session, it registers in the nearest region.
The mobile app discovers the session via the API, gets a region_url,
and connects its WebSocket directly to that region. No cross-region traffic.
Multi-Region
Each region runs its own AFK Backend, so session data stays local. Currently deployed in Bangalore (BLR), expanding to San Francisco (SFO) and Frankfurt (FRA).
Why This Matters for Terminal Streaming
Without multi-region: SF user → BLR backend = ~250ms RTT per keystroke
With multi-region: SF user → SFO backend = ~20ms RTT per keystroke
Cloudflare edge SSL: TLS handshake completes at nearest PoP, not origin
Security Properties
The two encryption layers protect against different threat models. Together, they provide defense-in-depth.
E2EE Protects Against
- Compromised backend server
- Network eavesdropping (even without TLS)
- Database breach
- Malicious infrastructure provider
Context Isolation Protects Against
- Routing bugs in backend code
- Cross-user data leakage
- Session ID confusion or collision
+CTX
Combined: Defense-in-Depth
No single point of failure. Even if one layer is bypassed, the other still protects your data. The system is fail-closed: if anything goes wrong, the GCM authentication tag prevents garbled data from being displayed — you get a decryption error, not a data leak.
Cryptographic Primitives
Summary of all cryptographic algorithms used in AFK.
| Purpose | Algorithm | Standard |
|---|---|---|
| Key exchange | X25519 ECDH | RFC 7748 |
| E2EE cipher | AES-256-GCM | NIST SP 800-38D |
| Context cipher | ChaCha20 | RFC 8439 |
| Key derivation | HMAC-SHA256 | RFC 2104 |
| Master key encryption | AES-256-GCM | NIST SP 800-38D |
For a higher-level overview of our security approach, see the Security page.
Security Overview