~/docs

Encryption

The keys, algorithms and formats shellz uses to encrypt, sign and verify recordings.

↗ .mdmcp

This page describes exactly how a recording is protected, from the server to your browser. The same design applies to servers and to containers.

Keys at a glance

KeyTypeWhere it livesUsed for
Session data key32 random bytesOnly in the recorder's memory during the session, and wrapped in each segment headerEncrypting one session's content (AES-256-GCM)
Org encryption keyX25519 key pairPublic half on every server. Private half: sealed by shellz (standard) or only in your team's browsers (Perfect Secrecy)Wrapping session data keys
Org signing keyEd25519 key pairSame as the org encryption keySigning policies and org key rotations
Server keyEd25519 key pairCreated on the server at enrollment; the private half never leaves it (containers keep it in memory)Signing segments and requests to shellz
Release keyEd25519Pinned in the agent and installersVerifying agent downloads and updates

All signatures use a domain-separation prefix (for example shellz:segment:v1:), so a signature made for one purpose can never be valid for another.

Encrypting a session

When a session starts, the recorder:

  1. Generates a 32-byte random data key for the session.
  2. Wraps the data key to the org's current X25519 public key:
    • generates an ephemeral X25519 key pair and computes the shared secret with the org public key;
    • derives a wrapping key with HKDF-SHA256, using the ephemeral and org public keys as salt and shellz:session-key:v1:<sessionId> as info;
    • encrypts the data key with AES-256-GCM under the wrapping key, with the org key ID as associated data.
  3. Records the session as two streams: an asciicast v2 event stream (output, input, resizes) and a plain-text transcript.

About once a second, the recorder closes a segment:

  1. The new events are serialized as JSON and compressed with gzip.
  2. The result is encrypted with AES-256-GCM under the data key, with a fresh 12-byte nonce, and with shellz:segment-content:v1:<sessionId>:<seq> as associated data. A segment can't be moved to another session or position.
  3. A header is built: org, server and session IDs, the sequence number, the SHA-256 hash of the ciphertext, the hash of the previous segment's header, the wrapped data key, the org key ID, a timestamp, and on the first segment the session details (kind, user, source address, terminal, container).
  4. The header is signed with the server's Ed25519 key.

Segments are at most 1 MiB. The recorder discards the data key when the session ends. Only the wrapped copy in the headers remains, so the server can't decrypt a session once it's over.

Hidden input

Keystrokes are only recorded while the terminal echoes them. Passwords typed at sudo, passwd, ssh or read -s prompts turn echo off, so they're never written to the recording.

The hash chain

Each header contains prevHash, the SHA-256 hash of the previous segment's header. The first segment of a session references a fixed genesis value, and the last one is marked final. Together they form a chain:

seq 0                 seq 1                 seq 2 (final)
┌──────────────┐      ┌──────────────┐      ┌──────────────┐
│ prevHash: 0… │◀─────│ prevHash: h0 │◀─────│ prevHash: h1 │
│ cipherHash   │      │ cipherHash   │      │ cipherHash   │
│ signature    │      │ signature    │      │ signature    │
└──────────────┘      └──────────────┘      └──────────────┘

When a recording is played, every segment is checked:

  • the server's signature over the header;
  • that the ciphertext matches cipherHash;
  • that sequence numbers are consecutive and every prevHash matches;
  • that nothing follows the final segment.

The result is shown as the integrity badge: verified, gap (a segment is missing, for example because a server lost its disk before uploading) or tampered (a signature or hash didn't match).

Upload and storage

The server uploads segments over HTTPS. Before accepting a segment, shellz checks that it comes from an enrolled server of your org, that the server's signature is valid, and that it extends the session's chain:

  • a segment with a bad signature is rejected and raises segment-signature-invalid;
  • a segment that conflicts with one already stored (same position, different content) is rejected, the session is shown as tampered, and a segment-conflict alert is raised;
  • a segment that skips ahead is stored, and the session is marked as having a gap.

Accepted segments are written once to object storage under a retention lock: for 365 days, they can't be overwritten or deleted by anyone, including shellz.

Servers also sign every request to the shellz API (heartbeats, alerts, policy and key sync) with their server key, so a stolen network position can't impersonate a server.

Decrypting in the browser

To play a recording, the browser:

  1. gets the session's data key, either from shellz (standard mode) or by unwrapping it itself with the org private key unlocked by your passkey (Perfect Secrecy);
  2. downloads the segments as ciphertext;
  3. verifies signatures and the chain as above;
  4. decrypts and decompresses each segment with the Web Crypto API.

Opening a recording requires a sign-in within the last 10 minutes, and each view is written to the audit log.

Policies and key rotation

Each server follows a policy: whether recording is on, what happens when the spool fills up, and whether removal is allowed. Policies are signed by the org's Ed25519 key and carry a revision number. A server only applies a policy that is signed by your org, names that server (or its fleet), and has a higher revision than the one it has. A replayed or downgraded policy is rejected with a policy-rejected alert.

A new org key is signed by the previous one, forming a chain back to the key the server pinned at enrollment. A server only switches to a new key whose chain it can verify, and raises org-key-rejected otherwise.

Releases

The installers and the agent verify every release against the pinned release key before installing it: an Ed25519 signature over the version, architecture, URL, SHA-256 hash and size. The agent refuses to install an older version than it runs.

Offline verification

shellz decrypt verifies and decrypts recordings without the dashboard or network access: given a directory of segment files and either the org private key or one session's data key, it checks the chain (and server signatures with --host-key) and writes .cast and .txt files. See the CLI reference.

Algorithms

PurposeAlgorithm
Content encryptionAES-256-GCM, 96-bit random nonces
Key agreementX25519
Key derivationHKDF-SHA256
SignaturesEd25519
HashesSHA-256
Compressiongzip
TransportTLS 1.2 or later