Documentação
Web Share overview
Streaming the desktop to a browser: reach, roles and encryption.
Nesta página
The desktop, in a browser tab
Web Share streams a running TDE desktop to a browser. TDE binds a small HTTP and WebSocket server, serves a terminal client, and fans the desktop's frames out to every connected viewer. Open the URL on a phone and you are looking at the same desktop, live — same windows, same agents, same output.
It is not a screen recording and not a VNC session. The canonical cell frame still travels as compact terminal diffs, but TDE-owned surfaces can also publish a semantic interaction tree and synchronized pixel geometry. The browser gets structure where the desktop owns it instead of guessing controls from pixels.
tde share start local
tde share urlFor the command grammar see the tde share reference. This page
is about the model: who can reach it, who can type, and what the encryption
actually protects.
Reach
Reach is how far the share is allowed to travel. There are four modes, and LAN is the default.
| Mode | Binds | Who can reach it |
|---|---|---|
| Loopback — "Only me" | 127.0.0.1 | Nothing outside this machine. |
| LAN — "Local network" (default) | 0.0.0.0 | Anything that can route to this host. |
| Tailscale — "Tailscale (private)" | 0.0.0.0 | Devices on your tailnet, via the tailnet address. |
| Public — "Public (internet)" | 0.0.0.0 | Anyone on the internet with the URL, via Tailscale Funnel. |
Note that only Loopback narrows the socket. LAN, Tailscale and Public all bind every interface — the difference between them is which address the URL points at and whether a funnel sits in front. A Tailscale share is still reachable from the local network by anyone who can guess the port and hold a valid token.
Public reach depends on a working Tailscale Funnel on the host. TDE enables it in the background and reports readiness or a specific reason — the funnel is not enabled for your tailnet, HTTPS certificates are off, MagicDNS is off. Until the funnel is up, the URL falls back to the Tailscale-private address, so a link that looks fine may not be openable from outside the tailnet. Check Control Center's funnel state before sending a public link to somebody.
Operator and viewer
Every share has two URLs, distinguished by which token is in the URL fragment.
| Role | Can see | Can type |
|---|---|---|
| Operator | Everything | Yes — one at a time |
| Spectator | Everything | No |
The spectator token is derived from the operator token, so there is one secret to manage. Multiple operators may connect; exactly one holds the controller lease and the rest wait in a queue, promoted automatically when the holder leaves. A spectator's input is refused outright. The operator can also flip the whole share to spectator-only, which strips input from everyone without disconnecting anybody.
The token lives in the URL fragment (after the #), which browsers never send
to a server. The client transmits only a short, non-reversible token id.
Presence: who connected
Every accepted connection is recorded as a viewer visit: a stable id, the peer
IP, a best-effort description of where it came from, the connect time, the
disconnect time and the duration. Visits are appended to
~/.tde/web-share/sessions.jsonl — up to a thousand of them — and rendered in the
Web Share panel in Control Center, month by month, with a live ONLINE pill for
current connections. A separate audit stream records link creation, rotation,
revocation, viewer attach and detach, controller changes and denied input.
Private, LAN and tailnet addresses are labelled locally with no lookup. A public IP is resolved best-effort by a single outbound HTTP request to a third-party geolocation service, off the connection path, so a failure just leaves the IP in the log.
End-to-end encryption
E2EE is always on. There is no plaintext mode: a client that does not advertise the encrypted-auth capability is rejected during the handshake, and a plaintext text frame after the handshake is a hard error.
The handshake is hybrid post-quantum: X25519 and ML-KEM-768 are both run, and their shared secrets are concatenated before key derivation. The session is secure if either primitive holds. Keys are derived with HKDF-SHA256 over a transcript hash of the exact handshake bytes — which binds the negotiated keys to the capabilities and the key material, so a downgrade cannot go unnoticed. The record layer is ChaCha20-Poly1305 with per-direction keys and a sequence-numbered nonce. The pre-shared secret in the schedule is the share token itself.
Because the LAN page is plain HTTP, the browser has no crypto.subtle — TDE ships
the primitives as WebAssembly instead, embedded in the binary and served from the
same port.
What is not encrypted by this layer: the initial hello and challenge, the static asset fetches, and the authenticated-but-cleartext record header. Everything after the challenge — frames, input, control state — is sealed.
An optional 6-digit PIN adds an out-of-band factor and is compared in constant time. Public reach always forces a PIN and a finite time-to-live. TDE generates the PIN itself; you cannot choose the digits.
Joining a share from inside TDE
You do not need a browser to join. The launcher and the Web Share panel both offer
Join Web Share, which opens a native viewer window. Paste the URL, type the
6-digit PIN when there is one, and TDE connects as a real client with the same
hybrid handshake — over TLS when the URL is https. Press Ctrl+W to leave;
Esc belongs to the remote desktop.
The viewer never rescales the remote grid: a larger remote desktop scrolls, a smaller one is centred. The same window can also mirror your own running share locally, without a socket, which is the fastest way to see what a viewer sees.
Phones: QR pairing and the mobile shell
The Web Share panel can render the operator URL as a QR code. Scanning it opens the share directly on a phone; the token rides in the fragment, so nothing is typed. On a phone the client switches to a touch-first shell that derives the terminal grid from the real device geometry, and the page installs as a PWA — which is why the token is stashed locally and replayed, since a home-screen launch cannot carry a URL fragment.
TDE also runs a separate mobile relay on port 9478 for handing a page from the
in-desktop browser to your phone. That relay mints its own short-lived session with
a 15-minute TTL and a 6-digit pairing code; the code is not in the QR, it is
displayed separately. Do not confuse the two QR codes: one opens the shared desktop,
the other pairs a phone with the browser.
Semantic controls on phones
TDE-owned surfaces publish a validated interaction tree aligned to the same canonical frame as the cell grid. Each node has a stable action and bounds; an operator action names both the node and the frame revision it came from. The host can therefore reject a stale tap instead of activating whatever happened to move under that coordinate.
The mobile shell uses this structure for touch-first controls and immediate controller feedback while the authoritative action makes its network round trip. It does not turn every terminal app into HTML:
| Surface | Browser path |
|---|---|
| TDE native UI | Cell frame + semantic nodes and actions. |
| Hosted CLI/TUI | Terminal emulation and the app's own mouse/keyboard protocol. |
| Media and chrome | Position-bound pixel assets and decoration geometry. |
Semantic UI is negotiated as an additive capability. Older clients keep the cell interface, and a native surface remains keyboard-operable even when the semantic layer is unavailable.
Pixel decorations over the wire
The TDE desktop draws rounded window corners, hairline borders and shadows as real pixels on terminals that support graphics. Web Share carries those decorations to the browser too — as geometry, not images.
The host sends a compact JSON frame describing each decoration in terminal-cell coordinates: shadows, hairlines, corner fills and chips, each with RGBA colours and a clip rectangle. The browser paints them onto a canvas overlaid on the terminal grid. A run-length mask of replaceable cells travels with every frame, and the client must intersect against it — that mask is what stops decorations from painting over live glyphs.
It is strictly opt-in: a client that does not request the capability never receives a decoration frame, and decorations are only forwarded when they match the base frame exactly. The result is that a phone gets the same chrome as a graphics terminal, without any image protocol. See Terminal graphics.
Limits worth knowing
| Behaviour | Detail |
|---|---|
| Port | 9477 by default; TDE tries the next few ports if it is taken. |
| Terminal client | xterm.js, loaded from a public CDN. An air-gapped viewer will not render. |
| Resize | The browser requests a grid between 80–240 columns and 24–100 rows. Only the controller may resize the host. |
| Viewer cap | There is none. |
| Kicking one viewer | Not supported. The controls are collective: stop, rotate the link, spectator-only, or let the TTL expire. |
| Input size | Bounded per frame; a viewer that floods the input queue is disconnected. |
| TTL | Optional. Public reach always sets one. |
Stopping a share revokes the link and pushes a close to every viewer, so clients show "disconnected" instead of a frozen screen. Rotating does the same and mints a new token.