End-to-End Encryption

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 shell

The 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 modelUse it whenRequires
Explicit-key E2EOperators 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 E2EAn 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 -- whoami

If 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 -- whoami

Explicit-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 commands

This 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-identity

Copy 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-identity

Copy 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 shell

Passing 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-device

Then connect normally:

rstream webtty exec --url rstrm://shell -- whoami

That 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 -- whoami

Automation 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>' \
  -- whoami

The server endpoint identity is public server material in this form:

encryption_key_id:encryption_public_key:signing_key_id:signing_public_key

It 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_key

It 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.json

The default authorized clients file for a server identity named shell is:

~/.rstream/webtty/authorized_clients/shell.json

Use --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.json

If 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/stderr

Registered 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 \
  --enroll

The 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-identity

Start 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-device

The 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 -- whoami

Workspace-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 session

In 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 decrypt

That 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:

  1. An owner or admin sets up Workspace Protection from the browser and saves the Recovery Kit offline.
  2. CLI, agent, or service hosts that need to open protected data enroll as trusted workspace devices.
  3. A registered WebTTY server is created with workspace_managed encryption.
  4. The remote host enrolls from an authenticated rstream project context.
  5. During enrollment, the trusted local workspace device pins the public workspace keyset into the server enrollment.
  6. 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 status

These 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.json

The 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 \
  --enroll

When 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 -- whoami

If 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 required

That 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 automation

Approving 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.