End-to-End Encryption
Configure WebTTY end-to-end encryption and endpoint authentication with explicit keys or workspace-managed trust.
WebTTY end-to-end encryption has two parts: terminal stream encryption and endpoint authentication. The client verifies the WebTTY server endpoint identity before sending terminal bytes, and the server verifies a signed client proof before it starts a process.
The engine still parses WebTTY protocol frames, authenticates rstream callers, applies policy, records metadata, and routes live sessions. It does not receive plaintext stdin, stdout, or stderr when E2E is active.
E2E is independent from the transport. TLS still protects the network connection. WebTTY E2E adds a second layer so terminal payloads remain encrypted across the rstream servers.
The handshake and payload path are easiest to read in one direction. The server first proves its endpoint identity. The client then signs the server nonce, command config, session key grant, and its own signing key. Only after those checks does terminal content move.
WebTTY server
|
| signed ServerHello
v
WebTTY client verifies known server identity
|
| signed ClientProof + encrypted session key grant
v
WebTTY server verifies authorized client key
|
| terminal session opens
v
client terminal
|
| plaintext stdin
v
WebTTY client encrypts payload
|
| protobuf frame with encrypted payload
v
rstream engine
|
| routes protocol, sees metadata, not terminal bytes
v
WebTTY server decrypts payload
|
| plaintext stdin on remote host
v
remote shellThe reverse payload path is the same for stdout and stderr. The server encrypts terminal output, the engine records and routes ciphertext, and authorized clients decrypt locally.
The practical consequence is important. rstream can still provide product features such as policy, session inventory, live attach, control transfer, timestamps, retention, and recordings, while terminal content remains opaque when E2E is enabled.
Choose a key model
There are two supported key models, and they solve different operational problems.
| Key model | Use it when | Requires |
|---|---|---|
| Explicit-key E2E | Operators want to trust WebTTY endpoint identities directly, similar to a known-host workflow. | Local WebTTY identities, known server endpoint identities, and authorized client signing keys. |
| Workspace-managed E2E | An organization wants workspace-wide client-held keys, trusted browsers/devices, Recovery Kit, encrypted recordings, and centralized approvals. | An Enterprise workspace with Workspace Protection enabled. |
The WebTTY protocol is the same in both models. What changes is how each endpoint establishes trust. Explicit-key E2E uses local known-server entries and authorized client signing keys. Workspace-managed E2E uses trusted workspace devices: the engine checks that the presented device credential is still active, and the WebTTY server verifies a fresh client proof against public workspace trust material pinned during enrollment. Recording decrypts use session key grants created for the browser or device that opened or joined the session. For registered servers, the client resolves the server policy at runtime. The operator configures the server once; normal users connect to rstrm://name.
Runtime resolution
Clients do not guess whether a WebTTY target requires E2E. For rstrm://... targets, the CLI and SDKs first use live engine inventory, WebTTY labels, and the local known-server store. That is enough for lightweight tunnels, disabled registered servers, and registered servers that use explicit endpoint keys.
The Control plane can be used by clients to resolve durable registered-server metadata and workspace-managed trust material. It is not part of the WebTTY server session runtime. For registered servers, the engine admits the protocol=webtty tunnel only after the server proves its enrolled identity. For workspace-managed E2E, the engine checks that the presented workspace device credential is still active, while the WebTTY server verifies the WebTTY client proof against pinned workspace trust.
Use --e2e only when you want a fail-closed assertion:
rstream webtty exec --url rstrm://prod-shell --e2e -- whoamiIf prod-shell does not resolve to an E2E server, the command fails instead of silently opening a transport-only session. In the normal registered-server path, users connect with the short command and the client resolves the required security mode from runtime metadata and trust material:
rstream webtty exec --url rstrm://prod-shell -- whoamiExplicit-key encryption
Explicit-key E2E is operator-managed. It works for lightweight WebTTY tunnels and for registered servers. The server owns a local WebTTY endpoint identity, clients trust the server endpoint identity before sending terminal content, and the server authorizes client signing keys before it starts a process.
This gives the explicit-key path two independent checks. The client verifies the server, and the server verifies the client.
remote host
creates WebTTY endpoint identity
|
| endpoint identity copied into client trust
v
operator device
stores known WebTTY server endpoint identity
creates its own WebTTY endpoint identity
|
| client signing key copied into server authorization
v
server verifies signed client proof before running commandsThis is the WebTTY equivalent of a known-host decision plus endpoint client authentication. If the remote server key changes, a cautious client stops before sending terminal bytes instead of silently trusting the new key. If the client cannot prove an authorized signing key, the server rejects the session before command execution.
Create a server identity on the machine that will run the WebTTY server:
rstream webtty identity create --name shell
rstream webtty identity show --name shell --endpoint-identityCopy the endpoint identity printed by the second command.
Create a client identity on the operator machine:
rstream webtty identity create --name operator-device
rstream webtty identity show --name operator-device --endpoint-identityCopy the client endpoint identity printed by the second command to the server host and authorize it once:
rstream webtty authorized-client add operator-device \
--identity shell \
--key '<client-endpoint-identity>'Start the encrypted server:
rstream webtty server -v \
--rstream \
--name shell \
--identity shellPassing a server identity makes the server require authenticated E2E. You can
also add --e2e as an explicit fail-closed assertion, but it is not required
when the identity source already makes E2E active. The server loads authorized
clients from ~/.rstream/webtty/authorized_clients/shell.json. New clients
added with authorized-client add are accepted for new sessions without
restarting the server. Removing a client blocks new sessions signed by that
identity.
On the client machine, store the server endpoint identity once:
rstream webtty known-server add shell \
--key '<server-endpoint-identity>' \
--client-identity operator-deviceThen connect normally:
rstream webtty exec --url rstrm://shell -- whoamiThat is the normal operator path. The client will verify the server proof from the local known-server store on later connections and use the associated client identity when the server requires a signed client proof.
For a direct WebSocket or bytestream endpoint, select the same local known-server entry explicitly because there is no rstream inventory target to match:
rstream webtty exec --url ws://127.0.0.1:8080 --known-server shell -- whoamiAutomation can also pass the server endpoint identity directly when no local trust store is desired:
rstream webtty exec \
--url rstrm://shell \
--identity operator-device \
--known-server-key '<server-endpoint-identity>' \
-- whoamiThe server endpoint identity is public server material in this form:
encryption_key_id:encryption_public_key:signing_key_id:signing_public_keyIt lets the client verify the server's signed ServerHello and wrap the terminal session key for that server. The authorized client key is public client signing material in this form:
signing_key_id:signing_public_keyIt lets the server verify the client's signed ClientProof. Neither value is a private key.
The default known servers file is:
~/.rstream/webtty/known_servers.jsonThe default authorized clients file for a server identity named shell is:
~/.rstream/webtty/authorized_clients/shell.jsonUse --authorized-client-key, RSTREAM_WEBTTY_AUTHORIZED_CLIENT_KEYS, or
--authorized-clients-file for CI, containers, and service-manager deployments
that need an explicit trust source without relying on the default
authorized-client file.
This model is close to SSH known hosts, with a WebTTY-specific client proof on
the server side. The client must know the expected server endpoint identity
before sending terminal bytes. If the known endpoint identity is missing or
does not match the server identity, the client fails before sending the command.
If the client signing key is not authorized by the server, the server rejects
the session before command execution. rstream ui follows the same fail-closed
rule: it can ask which local client identity to use only after the server
endpoint identity has already been pinned.
Before trust is established, the client fails before sending terminal content. Depending on whether the failure happens during local configuration or during the server handshake, the message points to the missing known server endpoint identity:
WebTTY server shell requires E2E, but no matching known server identity was found; add it with rstream webtty known-server add shell --key <server-endpoint-identity>
E2E client mode requires --known-server-key, --known-servers-file, RSTREAM_WEBTTY_KNOWN_SERVER_KEY, RSTREAM_WEBTTY_KNOWN_SERVERS_FILE, or ~/.rstream/webtty/known_servers.jsonIf the server requires authenticated E2E, an encryption-only server key is not
enough. The client needs the full endpoint identity, including the server
signing key, so it can verify the signed ServerHello.
If the server requires client authentication and the client has no matching endpoint identity, the server rejects the session before command execution. After both trust decisions are established, the command runs with this behavior:
command runs, terminal payloads are encrypted, and the engine can route but not read stdin/stdout/stderrRegistered server with explicit keys
For a persistent server that still uses operator-managed keys, create the server with explicit_key:
rstream webtty server create prod-shell \
--recording-policy recorded \
--encryption-policy explicit_key \
--enrollThe command creates the registered server record and enrolls the current host. Show the public endpoint identity that clients must pin:
rstream webtty identity show \
--identity-file ~/.rstream/webtty/identities/<server-id>.identity.json \
--endpoint-identityStart the registered server:
rstream webtty server -v --server-id <server-id>Copy the single line printed by identity show --endpoint-identity. That is
the public server endpoint identity the operator pins on the client side.
When the server record is created from one machine and run from another, create
without --enroll, then run the printed server enroll <server-id> command on
the runtime host before showing the endpoint identity.
Authorize operator devices the same way as a lightweight explicit-key server. The server stores authorized client signing material by server id:
rstream webtty authorized-client add operator-device \
--server-id <server-id> \
--key '<client-endpoint-identity>'On the operator machine, pin the registered server endpoint identity once:
rstream webtty known-server add prod-shell \
--key '<server-endpoint-identity>' \
--client-identity operator-deviceThe client resolves the registered server, sees that E2E is required, and loads known WebTTY server keys from the configured trust store. The operator does not need to pass --e2e for normal use, but adding it is useful in scripts that must fail if encryption is not active.
rstream webtty exec --url rstrm://prod-shell -- whoamiWorkspace-managed encryption
Workspace-managed WebTTY E2E uses Workspace Protection. The workspace has trusted browsers, trusted CLI/agent/service devices, a workspace Recovery Kit, and registered WebTTY servers enrolled from trusted runtime hosts.
Use this model when terminal data and encrypted recordings must be decryptable only from trusted workspace clients. It is the zero-trust path for managed WebTTY, and it requires an Enterprise workspace with Workspace Protection enabled.
Workspace-managed E2E removes operator-distributed server keys from the normal connection path. The client proves trusted-device or trusted-browser status with workspace credentials, and the WebTTY server verifies a protocol proof tied to the pinned workspace trust. The Control plane manages setup, inventory, and audit; the engine performs the runtime active-device check; the WebTTY server does not call the Control plane during a terminal session.
owner/admin trusted browser
|
| sets up Workspace Protection and stores Recovery Kit offline
v
workspace keyset
|
| encrypted grants to trusted browsers/devices
v
trusted CLI / trusted agent / trusted service
|
| resolves registered WebTTY server at runtime
v
workspace-managed WebTTY E2E sessionIn this model the CLI does not carry an operator-distributed server public key. It proves that it is a trusted workspace device, receives the authorized key grants for that workspace and server, and derives the WebTTY recipient material locally.
The runtime responsibilities stay split:
Control plane setup, inventory, approval, audit
Engine rstream auth, policy, active-device check, routing
WebTTY server shell process and WebTTY proof verification
Trusted client private keys, credential signing, local decryptThat split is what keeps the model practical. The engine can reject a newly revoked device before a new session starts, but it cannot decrypt terminal content. The WebTTY server can verify the fresh client proof without needing Control plane access during the session.
The setup order matters:
- An owner or admin sets up Workspace Protection from the browser and saves the Recovery Kit offline.
- CLI, agent, or service hosts that need to open protected data enroll as trusted workspace devices.
- A registered WebTTY server is created with
workspace_managedencryption. - The remote host enrolls from an authenticated rstream project context.
- During enrollment, the trusted local workspace device pins the public workspace keyset into the server enrollment.
- The server starts, and clients connect normally; E2E is resolved at runtime.
Enroll a CLI device:
rstream workspace device enroll --kind cli --label operator-cli
rstream workspace device statusThese commands use the workspace attached to the active project context. Use
rstream workspace list and pass --workspace <workspace-id> only when there
is no active project context or when the device is enrolled for another
workspace.
The workspace device files are separate from WebTTY server files:
~/.rstream/workspaces/<workspace-id>/devices/<device-id>.json
~/.rstream/workspaces/<workspace-id>/webtty/identities/<device-id>.identity.jsonThe first file proves the device is trusted for the workspace. The WebTTY identity under the workspace directory is the X25519 identity used for WebTTY key grants.
Create and enroll a workspace-managed registered server from the trusted runtime host:
rstream webtty server create prod-shell \
--recording-policy recorded \
--encryption-policy workspace_managed \
--enrollWhen creation and runtime enrollment happen on different machines, create the record from the operator machine and run the printed enrollment command from the trusted runtime host:
rstream webtty server create prod-shell \
--recording-policy recorded \
--encryption-policy workspace_managed
rstream webtty server enroll <server-id>
rstream webtty server -v --server-id <server-id>The runtime host must already be a trusted workspace device before enrollment. If it is not trusted, enrollment fails before local server trust is written. This keeps the workspace-managed path simple: the server can verify workspace-managed WebTTY client proofs from pinned public trust material, but it never receives workspace private keys.
If the host was trusted, enrolled the server, then lost and later regained trusted-device state, refresh the local server trust pin:
rstream webtty server trust <server-id>This does not give the server workspace private keys. It refreshes the public keyset identity the server must expect when it verifies workspace-managed WebTTY client proofs. server trust requires an active trusted workspace device and is a repair path, not a way to run a workspace-managed server from an untrusted machine.
After the server is enrolled from a trusted runtime host, a trusted device connects without passing recipient keys on each command:
rstream webtty exec --url rstrm://prod-shell -- whoamiIf the local CLI is not a trusted workspace device, resolution fails before terminal content is sent.
When the local CLI has not been trusted, resolution fails with:
trusted workspace device is requiredThat failure is not a setup inconvenience. It is the point of the workspace-managed model. rstream account authorization alone is not enough to open protected terminal data.
Browser and device trust
Trusted browsers unlock protected workspace data in the Dashboard. Trusted devices unlock protected workspace data for CLI, agent, and service workflows.
The distinction is intentional. A browser trusted in IndexedDB does not automatically make the local CLI trusted. A CLI device under ~/.rstream/workspaces does not automatically trust the browser. Both may belong to the same user, but they are different cryptographic devices with different local private keys.
This distinction matters for WebTTY. A user can be allowed by project permissions to open a session and still be unable to decrypt an E2E session from an untrusted browser or device.
Those trust records can belong to the same human user while remaining separate cryptographic devices.
same human user
|
+-- browser profile key -> Dashboard, replay UI, approval flows
|
+-- CLI device key -> rstream webtty exec, rstream ui, agents
|
+-- service device key -> daemons and automationApproving one of these does not approve the others. That makes revocation and device loss easier to reason about in enterprise environments.
Filesystem sidecar
The WebDAV filesystem sidecar adds file access next to command execution. It can list directories, read files, upload files, download files, and write files under a configured root. It is refused when WebTTY E2E is active.
The reason is product security, not implementation convenience. E2E protects terminal payloads. The filesystem sidecar is a separate file API. Exposing it next to an encrypted terminal would create one protected surface and one unprotected surface to the same host.
When E2E is required, move file content through the encrypted terminal channel or use a separate file transfer path with its own security model.
Crypto details
WebTTY E2E uses X25519 identities, HPKE X25519/HKDF-SHA256/AES-256-GCM key envelopes, and AES-256-GCM terminal payload encryption. The full key hierarchy, AAD, storage model, recording behavior, and standards references are documented in Encryption Model.