Skip to main content
Remote v2 is the typed protocol between the authoritative runtime and clients. It keeps business authorization on the authority and treats the transport route (Direct, Tailnet, Relay, LAN) as replaceable infrastructure. The complete wire protocol definition lives in the repository at docs/remote/protocol-v2.md, and crates/core/src/remote_v2.rs is the single source of truth at the wire level. This page covers only the boundaries you must respect in an implementation.

Two pairing paths

Vibex has two pairing paths with different forms. Do not mix them: <transport> is one of direct, tailnet, or self_hosted_relay.

The offer path

  • The desktop identity is generated in the runtime home and stored with 0600 permissions on Unix.
  • An offer is cancelable and single-use, and consuming it and creating the device grant happen in the same SQLite transaction.
  • An offer contains route candidates, the desktop public key, a permission summary, an expiry, and a one-time challenge. It does not contain a long-lived grant, keys, or workspace data.
  • Mobile parses the link locally and strips the fragment before processing it asynchronously.

The pairing-code path

  • Nine digits grouped as NNN-NNN-NNN, derived from a UUIDv4, with only its SHA-256 hash persisted.
  • The client claims the code at POST /api/v2/pairing/code/claim. The code appears only in the request body, never in the URL, so it does not reach proxies or access logs.
  • The endpoint shares per-peer rate limits and authentication-failure budgets with the other unauthenticated routes.
  • A claim is single-use: reuse, a malformed code, or an expired code returns remote_pairing_code_invalid / remote_pairing_code_expired and writes a pairing_code_rejected audit record.
  • A successful claim returns exactly one device grant, with the permission level fixed when the pairing code was created.

Connection strings and certificate pinning

The payload of vibex://pair#/code/<payload> is a JSON object:
tlsCertificateDer is optional. The certificate travels out of band with the connection string (from the server console, not over the network), and the client pins it before issuing the first TLS request, so a self-signed LAN setup needs no public CA. The fingerprint format is sha256:<base64url(sha256(DER))>. A pinned link must use https and point to a numeric LAN address (loopback, RFC1918, or link-local / unique-local IPv6); otherwise it returns remote_pairing_link_invalid. This restriction is deliberate: pairing must not become a way to bypass the public CA system. The server identity key and the TLS certificate are pinned separately; the former comes from serverIdentityPublicKey in /api/v2/info.

Handshake and keys

The implementation uses mature primitives such as X25519, HKDF-SHA256, HMAC-SHA256, and ChaCha20-Poly1305.
  • A connection starts with control/hello and completes the handshake on control/server_info; the protocol uses range negotiation and currently selects 2.0.
  • A WebSocket ticket is valid for 30 seconds and single-use, exchanged through a controlled subprotocol and not placed in a URL.
  • The hello proof binds the ticket challenge, the full hello transcript, the server identity, the session epoch, the device identity, and the client ephemeral key.
  • server_info returns the server ephemeral key and the session-key confirmation.

Frame types

Unknown client, control, JSON message, attachment, binary frame, timeout, close, and transport enum values all decode to unknown; an unknown activity message closes with a structured protocol reason instead of panicking. The legacy 0.4 HTTP and /ws routes are compatibility endpoints; new clients use /ws/v2.

RPC, events, and sync

Every business operation passes device authentication, permission, and workspace authorization. Mutations use bounded idempotency keys; file operations additionally use content revisions / CAS. When a client detects a sequence-number or cursor gap, it returns resync_required and points at an authoritative operation to re-fetch. Attachment streams re-authenticate and re-authorize their domain before they start. A terminal attachment carries workspace scope, and terminal input requires generation checks, authorization, and auditing, and keeps no raw bytes. After a device grant is revoked, every active connection for that device immediately receives device_revoked. When the runtime shuts down, it sends server_shutdown, drains listeners, and releases sockets.

Permission model

RemoteDevicePermissionLevel has three values, serialized as read_only / approve_only / full_control: The level is fixed on the authority when the offer / pairing code is created; a client cannot raise its own privileges.

Gateway endpoints

Relay constraints

Relay is a zero-knowledge room router: it only forwards opaque encrypted frames. It may expose the routing metadata that /health, /api/info, and the WebSocket bridge need, but it does not perform business authorization, read or write workspaces, or store provider profiles or timelines. The endpoints on Relay are /health, /api/info, /ws, /api/rooms/{room_id}/pair|command, and two /api/push/* routes. There is no /api/v2/info on Relay — that is an endpoint of the authoritative runtime. Local checks:

Connection strategy

The client can probe and fall back between Direct, Tailnet, Relay, and LAN. After a route recovers, probe again and switch, without pairing again; if the device has been revoked, every route must fail.

Deployment

Before you publish to the public internet, configure HTTPS/WSS, Host/Origin validation, and connection and room limits, and read docs/smoke/relay-nat.md and docs/smoke/remote-lan.md in the repository.

Logging and evidence requirements

Do not record the following in Relay logs or test evidence: tokens, pairing codes, private keys, prompts, file paths, terminal content, Git diffs, provider settings, raw ciphertext, or nonces. Relay logs should contain routing, counts, and timing metadata only.