Encryption Model
Cryptographic design for transport security, Workspace Protection, and WebTTY E2E.
This page describes the cryptographic model behind rstream transport security, Workspace Protection, and WebTTY end-to-end encryption. It is written for security reviewers who need to understand which keys exist, where private material lives, what the rstream servers can see, and which failures are treated as security failures.
rstream uses a layered model. Transport encryption protects network connections. Token and certificate authentication decide who can use the platform and the runtime. Workspace Protection adds client-held workspace keys for selected protected data. WebTTY end-to-end encryption adds terminal stream encryption and endpoint authentication while still allowing the engine to route, authorize, record metadata, and coordinate live sessions.
Read this first diagram as the global stack, not as a WebTTY-only design. WebTTY E2E is one protected-data use case inside the broader rstream security model.
Application payload
WebTTY stdin/stdout/stderr, selected protected workspace data
|
| optional client-side encryption
v
rstream protocol and product metadata
sessions, participants, labels, policy, event timestamps
|
| authenticated runtime connection
v
Transport security
TLS 1.3, configured mTLS, configured QUIC
|
v
Network pathThe layers are not interchangeable. TLS protects the network path. Token and certificate policy protect who can use a runtime surface. Workspace Protection and WebTTY E2E protect selected payloads from server-side plaintext access.
Security goals
The first goal is to avoid exposing private networks. A remote machine running rstream initiates an outbound connection to the engine. Operators, browsers, agents, and services connect through authenticated rstream paths instead of requiring the machine to expose an inbound administrative port.
The second goal is to keep authorization centralized without forcing every upstream service to implement the same policy. Tokens, mTLS credentials, project access policies, resource boundaries, and WebTTY server policies are evaluated before traffic reaches the remote machine.
The third goal is to make selected data unreadable to rstream servers. Workspace Protection is designed so protected workspace material is opened only by trusted browsers, trusted devices, or the Recovery Kit. WebTTY E2E is designed so terminal stdin, stdout, and stderr can remain encrypted even when the engine parses WebTTY protocol messages.
These goals are complementary. Transport security is always required. End-to-end encryption is an additional boundary for payloads that must not be readable by rstream infrastructure.
Threat model
The model reduces the impact of a compromised control plane, engine, database, or storage layer. When Workspace Protection is active, the server stores public keys, encrypted envelopes, signatures, verification metadata, and audit metadata. It does not store the private workspace key material in a form it can open by itself. When WebTTY E2E is active, session recordings store ciphertext for terminal payloads, not plaintext terminal bytes.
The model does not make the remote operating system safe if the remote host itself is compromised. A WebTTY server runs commands on that host, and the host can observe its own processes, shell environment, filesystem, and terminal output. The model also does not protect a trusted browser or trusted device after its local private keys are stolen. A trusted client is part of the trust boundary.
The server remains authoritative for metadata and policy decisions. A malicious server can deny access, omit events, refuse to route traffic, hide stale metadata, or present a fake availability state. Client-held encryption prevents it from decrypting protected payloads, but it does not make the server unable to disrupt service.
Trust boundaries
The rstream client, CLI, SDK, browser, or WebTTY server holds local credentials. This can include rstream login material, mTLS private keys, WebTTY X25519 identities, and Workspace Protection device keys.
The control plane stores workspaces, projects, registered WebTTY server records, credentials, policy, key envelopes, public keys, signatures, audit events, and metadata. It can authorize actions and return encrypted material, but it is not supposed to hold the private keys needed to decrypt protected workspace data.
The engine handles runtime connections. For WebTTY, it can parse protobuf frames, authenticate callers, resolve a registered server, create managed session metadata, route stdout and stderr to participants, accept stdin only from the active controller, and record ordered events. With WebTTY E2E active, the engine still sees frame type and metadata, but terminal stdin, stdout, and stderr bytes are carried as encrypted payloads after the server and client endpoint proofs have been checked.
The database and object storage hold durable product state. For WebTTY recordings, plaintext sessions may store plaintext payloads according to recording policy. E2E sessions store ciphertext and crypto metadata. Decryption of E2E recordings happens on trusted clients.
Workspace-managed WebTTY splits responsibility across those boundaries. Each component has a narrow security role, and no server-side component has enough material to open protected terminal bytes by itself.
| Component | Responsibility | Boundary |
|---|---|---|
| Control plane | Stores durable trust state, registered server inventory, policy, encrypted envelopes, approvals, and audit metadata. | It is not in the terminal data path and cannot decrypt WebTTY payloads. |
| Engine | Authenticates the runtime caller, applies project and WebTTY policy, checks that the presented workspace device credential is still active, and routes accepted sessions. | It cannot forge the WebTTY client proof and cannot decrypt terminal payloads. |
| WebTTY server | Runs the remote process and verifies the WebTTY client proof against the public workspace trust material pinned during enrollment. | It does not call the Control plane during a terminal session and never receives workspace private keys. |
| Trusted browser, CLI, agent, or service device | Holds private keys locally, signs workspace credentials, verifies endpoints, and decrypts protected data. | Its private key material is not delegated to rstream infrastructure. |
This split is deliberate. The engine can refuse a revoked device before a new session starts, but it cannot create the cryptographic proof expected by the WebTTY server. The WebTTY server can verify that the connecting client proves possession of trusted key material, but it does not need live Control plane access to do it. The Control plane can coordinate approval, policy, and audit, but it cannot read the E2E terminal stream.
Transport security
Managed client-to-engine sessions use TLS 1.3. The rstream protocol then carries runtime control operations and tunnel traffic over that authenticated transport.
Published tunnel entrypoints have their own edge policy. Depending on the deployment and tunnel configuration, a published endpoint can enforce minimum TLS version, mTLS client certificate authentication, post-quantum key exchange preferences, Encrypted Client Hello policy, token authentication, rstream Auth, and project access policies before traffic is forwarded upstream.
Transport security protects traffic on the network. It does not by itself stop the engine from reading decrypted traffic after TLS termination. WebTTY E2E is the additional layer used when terminal payloads must stay opaque to rstream servers.
Workspace Protection
Workspace Protection is a workspace-wide client-side key model. It is not specific to WebTTY. WebTTY uses it for workspace-managed terminal encryption and encrypted recordings, but the same workspace key model can protect other workspace data over time.
Workspace Protection requires an Enterprise workspace. It is different from explicit-key WebTTY E2E, which can be configured directly by clients and servers without workspace-managed key custody.
Workspace Protection follows the same principle. The private workspace bundle is created locally and then encrypted into envelopes for trusted clients and the Recovery Kit.
The diagram below shows the workspace-level key envelope model. WebTTY is one consumer of this model, but the workspace keyset is not owned by WebTTY.
Owner/admin trusted browser
|
| creates workspace keyset locally
v
workspace private bundle
|
| encrypted envelopes
|------------------------------.
v v
trusted browser/device keys Recovery Kit
|
| local decrypt only
v
protected workspace dataThe control plane stores the public workspace keys, encrypted envelopes, signatures, hashes, and approval metadata. It does not need the unwrapped private workspace bundle to authorize normal product operations.
The Workspace Protection suite is:
p256-hkdf-sha256-aes-256-gcmEach trusted browser or trusted device has two local P-256 key pairs. The ECDH key pair decrypts workspace key envelopes. The ECDSA key pair signs trust, lookup, approval, revocation, and recovery payloads. Browsers store their private keys in browser storage. CLI, agent, and service devices store their private keys under the workspace device directory.
The workspace keyset also has P-256 encryption and signing key pairs. The private workspace bundle is encrypted to trusted devices and to the Recovery Kit. The server stores the public workspace keys, encrypted envelopes, hashes, signatures, and metadata. It does not need the decrypted private workspace bundle to authorize a request.
Envelope encryption uses P-256 ECDH to derive a shared secret with an ephemeral sender key, HKDF-SHA256 to derive an AES key, and AES-256-GCM with a 96-bit nonce to encrypt the workspace private bundle. The HKDF info string is:
rstream.workspace_key_envelope.v1The envelope records the crypto suite, the sender ephemeral public key, HKDF salt, nonce, and ciphertext. Recipients decrypt locally with their trusted device private key or Recovery Kit private key.
Trusted browsers and devices
Trusted browsers and trusted devices are intentionally separate. A browser key in IndexedDB is not a CLI device. A CLI, agent, or service device under the rstream home directory is not a browser. Both can be trusted for the same workspace, but each has its own key pair, approval path, and revocation state.
New devices generate private keys locally and send only public keys, a fingerprint, device metadata, and signed proof material to the server. A trusted approver compares a verification code with the requester. That code is derived from a canonical payload containing the workspace id, device id, device kind, public encryption key, public signing key, optional WebTTY public key, optional WebTTY key id, and fingerprint.
The verification code is short enough for humans to compare, but it is bound to the public key material. If the server silently replaces the requester public key, the verification code changes. Approval happens only after both sides see the same code.
The canonical payloads are encoded with deterministic JSON: object keys are sorted, undefined values are omitted, and non-finite numbers are rejected. Signatures are made over that canonical representation.
Recovery Kit
The Recovery Kit is a workspace-wide offline recovery path. It contains recovery private key material that can decrypt an encrypted envelope for the workspace private bundle. Save it outside the normal rstream account path.
Losing every trusted browser, every trusted device, and the current Recovery Kit makes protected workspace data unrecoverable. rstream cannot recreate the private workspace bundle from server-side data alone.
Rotation creates new recovery material and a new encrypted recovery envelope. The previous Recovery Kit is treated as invalid only after the replacement is active and stored offline.
WebTTY E2E
WebTTY E2E protects terminal stream bytes and authenticates WebTTY endpoints. It does not encrypt the whole WebTTY protocol. The engine must still parse protocol messages to route sessions, enforce access policy, coordinate live attach/control, record timestamps, and keep session lifecycle state.
For WebTTY, the boundary is inside the protocol. The engine can process protocol and metadata, while terminal bytes are encrypted for authorized clients and servers.
The next diagram follows one WebTTY session from endpoint authentication to encrypted terminal routing. It is a concrete example of the broader layered model described above.
WebTTY server
|
| signed ServerHello
v
WebTTY client verifies endpoint identity
|
| signed ClientProof and encrypted session key grant
v
WebTTY server verifies authorized client key
|
| encrypt terminal payload
v
protobuf frame
visible: frame type, stream, session metadata, sizes
hidden: stdin/stdout/stderr plaintext
|
v
rstream engine
|
| route, authorize, record metadata/ciphertext
v
authorized client/server decrypts locallyThe WebTTY protobuf envelope remains visible. Terminal payloads move from plaintext data fields to encrypted payload fields. The encrypted payload contains ciphertext, plaintext length, and crypto metadata. The engine can see that a stdout event occurred, when it occurred, which session it belongs to, and how large the payload is. It cannot decrypt the bytes without a recipient private key.
The endpoint-authentication proof is separate from rstream account authorization. ServerHello binds the server endpoint identity, nonce, supported suites, transport, project/server context when available, and auth requirement. ClientProof binds the command config, session key grant, server nonce, client signing key, and a short validity window. This prevents a client from sending terminal bytes to an unexpected server and prevents an E2E server from becoming an unauthenticated command runner.
WebTTY identities use X25519. Public keys and private keys are 32 bytes. A WebTTY identity key id is the first 16 bytes of:
SHA-256("rstream-webtty-e2e-key-id/v1" || public_key)For each encrypted session, the client creates a 32-byte AES payload key and a 16-byte payload key id. The payload key encrypts terminal stdin, stdout, and stderr. The payload key is wrapped for each recipient with HPKE using X25519, HKDF-SHA256, and AES-256-GCM.
The nominal payload suite is:
aes-256-gcmThe nominal key envelope suite is:
hpke-x25519-hkdf-sha256-aes-256-gcmThe protocol reserves identifiers for future suites, but implementations reject unsupported suites. Accepting an unknown suite would downgrade the fail-closed behavior expected from remote shell tooling.
WebTTY recipients
An encrypted WebTTY session must have at least one recipient. Explicit-key workflows use known WebTTY server public keys that the operator has accepted locally. Workspace-managed workflows use trusted workspace devices, registered server enrollment, and key grants resolved from Workspace Protection.
Recipient kinds include public keys, users, workspace devices, workspace keysets, and servers. The key context binds a session to the intended workspace, project, registered server, and recipients when those fields are available. That context is authenticated by payload and key-wrap AAD.
Key wrapping
The HPKE info value is domain separated with:
rstream-webtty-e2e-key/v1It also binds the payload cipher suite, key envelope suite, payload key id, and key context. The HPKE AAD binds the recipient key id, the payload suite, the key envelope suite, the payload key id, and the same key context.
This means a wrapped payload key is not just "a key encrypted to a recipient". It is a key encrypted for a specific session key grant context and suite selection. Reusing the wrapped key in another context fails authentication.
Payload encryption
Each terminal payload uses AES-256-GCM with a 96-bit nonce generated for that payload. The plaintext length is recorded separately and authenticated. Decryption verifies both the AEAD tag and the decoded plaintext length.
Payload AAD is domain separated with:
rstream-webtty-e2e-payload/v1The payload AAD binds the stream name (stdin, stdout, or stderr), payload suite, payload key id, key context, nonce, and plaintext length. A ciphertext produced for stdout cannot be replayed as stdin without changing the AAD and failing authentication.
What rstream can see
With WebTTY E2E active, rstream can see session metadata: workspace id, project id, server id, tunnel id, session id, participant ids, controller state, timestamps, event sequence, stream direction, payload length, crypto suite identifiers, payload key ids, recipient key ids, and key envelope metadata.
rstream cannot see plaintext stdin, stdout, or stderr. Private keys, wrapped payload keys, and decrypt material do not belong in general metadata APIs, logs, webhooks, or event streams. Decrypt material belongs behind dedicated APIs that require both authorization and trusted local key material.
Metadata visibility is intentional. It enables inventory, session recording metadata, audit trails, live collaboration, retention policy, and control transfer without giving the server plaintext terminal content.
| Data | Transport-only WebTTY | WebTTY E2E |
|---|---|---|
| Session metadata | Visible to rstream | Visible to rstream |
| Protocol frame type and stream direction | Visible to rstream | Visible to rstream |
| Payload length and event order | Visible to rstream | Visible to rstream |
| Plaintext stdin/stdout/stderr | Visible where the engine terminates plaintext | Not visible to rstream |
| Stored recording payload | Plaintext when recording policy allows it | Ciphertext plus crypto metadata |
| Decryption material | Not needed | Dedicated decrypt-material path only |
Recordings and replay
Managed WebTTY recordings store ordered events. Each event can include stream direction, timestamp, sequence cursor, resize metadata, terminal mode metadata, plaintext payload for non-E2E sessions, or ciphertext plus crypto metadata for E2E sessions.
Text export is designed to behave like terminal scrollback. It renders stdout and stderr by default and excludes stdin unless requested. Alternate-screen applications such as htop, vim, or less are treated like a terminal save: if the application exits and restores the main screen before export, its transient screen is not rendered into the text log. If export happens while the alternate screen is still active, the active screen snapshot can be included.
The JS SDK exposes the same replay helpers used by product UI and external tools:
import { createWebTTYE2EReplayPayloadCryptoFromKeyGrant } from "@rstreamlabs/webtty";
import { decryptWebTTYRecordedTextLog } from "@rstreamlabs/webtty";
import { renderWebTTYRecordedTextLog } from "@rstreamlabs/webtty";renderWebTTYRecordedTextLog renders plaintext events into a readable log. decryptWebTTYRecordedTextLog decrypts E2E events with a payload crypto object and renders the result. createWebTTYE2EReplayPayloadCryptoFromKeyGrant builds that payload crypto object from authorized decrypt material and a local WebTTY identity.
Local storage
WebTTY server identities live under:
~/.rstream/webtty/identities/<name>.identity.jsonRegistered server enrollments live under:
~/.rstream/webtty/enrollments/<server-id>.yamlExplicit-key known server keys live under:
~/.rstream/webtty/known_servers.jsonWorkspace-managed CLI, agent, and service devices live under:
~/.rstream/workspaces/<workspace-id>/devices/<device-id>.json
~/.rstream/workspaces/<workspace-id>/webtty/identities/<device-id>.identity.jsonThis split is deliberate. Files under ~/.rstream/webtty are WebTTY-local. Files under ~/.rstream/workspaces belong to Workspace Protection, which can protect more than WebTTY.
Private key files must be readable only by the local account or service identity that uses them. Services use the operating system secret manager or tightly permissioned files when running WebTTY as a daemon.
Failure behavior
Security-sensitive failures are fail closed. Missing E2E recipients prevent encrypted session creation. Missing known server keys prevent explicit-key clients from sending terminal content. Missing trusted workspace device material prevents workspace-managed E2E resolution. Unsupported suites are rejected. Invalid signatures are rejected. Expired signed proofs are rejected. E2E WebTTY refuses the filesystem sidecar.
The --e2e client flag is a fail-closed assertion, not a setup requirement. Registered servers resolve their encryption policy at runtime. If a user adds --e2e and the resolved server is not encrypted, the client fails instead of silently opening a non-E2E session.
| Failure | Expected behavior |
|---|---|
| Workspace device not trusted | Refuse protected workspace-managed resolution |
| WebTTY server key changed unexpectedly | Refuse to send terminal payloads |
| Recovery Kit and all trusted clients lost | Protected data is unrecoverable |
| Unsupported crypto suite | Reject instead of attempting fallback |
| E2E combined with filesystem sidecar | Reject the configuration |
Code inspection
The public client and SDK implementations are inspectable:
- rstream-go contains the rstream CLI, Go WebTTY client/server, WebTTY protocol handling, X25519 identity files, HPKE key wrapping, AES-GCM payload crypto, and runtime tests.
- rstream-js contains the browser WebTTY client, WebTTY E2E crypto helpers, and recording replay/decryption helpers.
- rstream-cpp contains C++ WebTTY client/server support for the plain and WebSocket transports.
The control plane implementation is not required to inspect WebTTY payload encryption. Client-side encryption, decryption, key wrapping, key id derivation, and replay decryption are implemented in the public SDKs.
Standards
TLS 1.3 is the transport security baseline for managed client-to-engine connections and is specified by RFC 8446. It protects the network connection, but it is not the same as WebTTY E2E because TLS terminates at the runtime endpoint.
QUIC transport is specified by RFC 9000. rstream can use QUIC as a client-to-engine transport choice. That choice changes the transport path, not the WebTTY payload encryption model.
HPKE is specified by RFC 9180. WebTTY uses HPKE over X25519, HKDF-SHA256, and AES-256-GCM to wrap per-session payload keys for authorized recipients.
X25519 is specified by RFC 7748. WebTTY identities use X25519 because it is compact, widely implemented, and directly supported by HPKE suites.
HKDF is specified by RFC 5869. Workspace Protection uses HKDF-SHA256 after P-256 ECDH, and WebTTY HPKE uses HKDF-SHA256 inside the selected HPKE suite.
AES-GCM is specified by NIST SP 800-38D. rstream uses AES-256-GCM for Workspace Protection envelopes and for WebTTY payload encryption, with 96-bit nonces and authenticated associated data.
Review checklist
When reviewing an rstream deployment, verify three separate layers. First, check transport and authentication, including TLS policy, token scope, mTLS policy, and project access policy. Second, check workspace trust, including trusted browsers and devices, Recovery Kit status, revocation policy, and local key storage. Third, check WebTTY policy, including registered server status, encryption policy, recording policy, filesystem sidecar state, session permissions, and client runtime behavior.
Those layers are independent by design. A user can be authorized by token policy and still fail to decrypt protected data because the browser or device is not trusted. A WebTTY session can be transport-encrypted but not end-to-end encrypted. A workspace can have Workspace Protection without every WebTTY server using workspace-managed E2E.