Security

Encrypted between your Macs—not merely to the relay.

Screen Ferry authenticates Host and Client to each other, derives session-specific traffic keys at the endpoints, and seals every application datagram above both relay and P2P carriers.

The security boundary

flowchart LR
  H["Host · static + fresh ephemeral keys"]
  R["Relay · validates legs · forwards opaque bytes"]
  C["Client · static + fresh ephemeral keys"]
  H ==>|"ChaCha20-Poly1305 sealed session"| C
  H -. "QUIC relay carrier" .-> R
  R -. "opaque datagrams" .-> C
  H -. "qualified direct UDP carrier" .-> C
Application encryption spans Host and Client. The relay participates in routing and leg validation, not application decryption.

Device identity

Each Mac has a static Curve25519 identity key. Host and Client already know the peer identity expected for the session.

Mutual key agreement

A two-message, Noise-KK-shaped X25519 exchange combines ephemeral-static, static-static, ephemeral-ephemeral, and static-ephemeral results.

Session binding

The relay session ID is mixed into the key-exchange transcript, tying the encrypted channel to the specific brokered session.

Endpoint-only traffic keys

Host and Client derive the application traffic keys. The relay holds separate leg-credential key material and cannot decrypt sealed Screen Ferry application datagrams.

ChaCha20-Poly1305

Every application datagram is confidential and tamper-evident.

Each sealed datagram authenticates a 64-bit sequence number as associated data. A modified header or payload fails authentication instead of becoming corrupted media or control input.

The receiver maintains a sliding 1,024-datagram replay window. Legitimate network reordering is accepted inside the window; duplicates and over-aged datagrams are rejected.

Key ratcheting

Direction-specific keys advance every 2²⁰ datagrams.

Host-to-Client and Client-to-Host traffic never share direction key material. The authenticated sequence number determines the epoch, allowing both endpoints to advance without renegotiating the session.

Superseded Screen Ferry key boxes are erased during ratchet and teardown. CryptoKit-owned ephemeral private-key references are released when exchange completes or fails.

flowchart LR
  K0["Direction key · epoch 0"] -->|"2²⁰ datagrams"| K1["Direction key · epoch 1"]
  K1 -->|"2²⁰ datagrams"| K2["Direction key · epoch 2"]
  K0 -.-> E0["Superseded material erased"]
  K1 -.-> E1["Superseded material erased"]
Each direction ratchets independently, with the authenticated sequence number selecting the traffic-key epoch.

Content-blind relay

Opaque datagrams in. Opaque datagrams out.

The Rust data plane validates credentials bound to session, device, role, and relay leg before forwarding. Its hot path moves the application datagram bytes verbatim.

It handles the connection information required to authenticate devices, route sessions, enforce service controls, and report operational counters. It does not decode Screen Ferry video, audio, input, cursor, clipboard, or control messages.

Same protection on every route

Migration is not a security downgrade.

Application sealing lives above the carrier. The same session keys, authenticated sequences, replay defense, and ratchet protect traffic over the QUIC relay and a direct UDP path.

Malformed and unauthenticated traffic is counted and rejected and can trigger route revalidation.