Skip to content
TraceMote
How it works

One connection, from click to close

Nothing here is a black box. This is the actual path a session takes through the system, including the two places it can be refused and the one place it can be ended.

One connection, end to endscroll →
YOUR PEOPLE’S DEVICESYOUR RENDEZVOUS HOSTYOUR BACKENDControllersigned in, holds a tokenrustdesk GUIControlled deviceregistered to an ownerrustdesk --serverhbbsrendezvous — sole enforcement point:21116 tcp+udphbbrrelay — unmodified:21117 · fallback onlyAuth APIdecisions, grants, audit, disconnects:21114 · fastifyMongoDBusers · grants · logsAdmin consolebrowser only — never speaks to hbbsnext.jsPunchHoleRequestRegisterPk · keepalive/internal/authorize{ allow, perms, ref }REST + session cookiedirect P2Pencrypted end to endrelay fallbackheartbeat · audit · sysinfo{ "disconnect": [conn_id] } — ends a live session
Everything in this picture runs on infrastructure you control. The relay only ever carries the fallback path, which is why session termination is delivered over the heartbeat instead — it reaches direct connections too.
Step by step

What happens, in order

Five steps. The interesting one is the fourth.

01

Hand out a branded client

The installer already knows your rendezvous server, your relay, your API and your key — and that login is required.

The configuration is signed with your key at release time and verified by the client at startup, so a bundle that has been tampered with is discarded rather than trusted.

02

A person signs in

The client's account UI is upstream's; the backend answering it is yours. A token lands on the device and travels with every connection it makes.

The rendezvous connection is encrypted before the token crosses it — the server offers its signed key exchange unprompted, which upstream's own server never does.

03

The device registers to an owner

Registration is tied to an account. A device nobody owns is told so with a code the client already understands.

Registration deliberately does not fail closed: if it did, a backend outage would take every device offline at once and leave the emergency path nothing to reach.

04

A connection is authorized, or it is not

Before the two peers are introduced, the rendezvous server asks your API: this user, this device, this kind of connection — yes or no?

A no is returned as a sentence the user sees verbatim. A yes carries a permission set and an audit reference to the controlled device, in fields the protocol already has.

05

The session is logged — and can be ended

Connection and file-transfer events land against one session record. The console can close that session, or revoke the grant behind it.

Termination rides the heartbeat the controlled device already sends, so it reaches direct peer-to-peer sessions that never touch the relay.

the authorization call
POST /api/internal/authorize
{
  "token":     "<the signed-in user's token>",
  "from_id":   "482913604",
  "to_id":     "771204338",
  "conn_type": "remote_desktop"
}

200 OK
{
  "allow":       true,
  "audit_ref":   "c2f1…",
  "permissions": { "clipboard": false, "file_transfer": false }
}

One request, one decision. The client retries a slow connect up to three times, so the rendezvous server caches the answer — otherwise a single connect would produce three decisions and three audit references, two of them describing sessions that never existed.

The decision

Allow carries more than a yes

An approval hands the controlled device two extra things, in fields the protocol already had and upstream never used.

Controller
The person connecting
PunchHoleRequest { id, token }
hbbs
Rendezvous — the only enforcement point
licence key · peer exists · online
Auth API
Your backend, your database
{ allow, reason, permissions }
Controlled device
Introduced only on an allow
PunchHole + permissions + audit ref

Allow. The two peers are introduced, the grant’s permission set travels with the introduction, and an audit reference ties every later event to this one session.

Deny. No introduction happens, and the reason is shown to the person verbatim — “Please sign in to connect to this device.” — rather than a timeout they will file a ticket about.

The pieces

Five components, three of them upstream's

The client

Rust core and a Flutter UI — upstream's, with a login gate added

fork

Capture, codecs, input and transport are untouched. What we added is a flag that makes login mandatory, gates on the two places a connection starts, and a registration gate in the service process. With the flag off, the binary behaves exactly like upstream's.

hbbs — rendezvous

Introduces two peers, and is the sole enforcement point

fork

It already checked the licence key, that the device exists, and that it registered recently. We added the question that matters: may this signed-in person reach this device? Both of its connection paths ask it.

hbbr — relay

Copies bytes between two streams. Deliberately unmodified

unmodified

It has no concept of a user, a device or a session, and it only ever sees the fallback path. Leaving it alone keeps one enforcement point instead of two half-enforcement points.

The auth API

Decisions, accounts, grants, audit, disconnects

ours

A small Fastify service over MongoDB. It answers the rendezvous server's authorization call, serves the endpoints the client already knows how to speak, and is the only thing the console talks to.

The admin console

A browser client, and nothing more

ours

It renders no data on the server and holds no secrets. Its route guards are navigation, not security — the boundary is on the API, which re-reads the user from the database on every request.

Ports

What listens where

Six ports, and one of them is not for the internet. The API's number is not arbitrary — a client that is not told otherwise derives it from the rendezvous port.

PortProtocolServicePurposeExposure
21114TCP / HTTPapps/apiAuth API. The client derives this as rendezvous port − 2 unless api-server is set.Public (put TLS in front of it)
21115TCPhbbsNAT-type test listener — and, from loopback, the runtime console.Not for the internet
21116TCP + UDPhbbsRendezvous. UDP registers and keeps alive, TCP carries punch-hole and relay signalling.Public, both protocols
21117TCPhbbrRelay fallback for peers that cannot punch a hole.Public
21118TCPhbbs / clientWebSocket rendezvous; also the client's direct-access port.Public if you need web clients
21119TCPhbbrWebSocket relay.Public if you need web clients
Get started

Read the rest of it

The architecture notes, the deployment guide and the patch map are all in the repository, written for the person who has to operate this.