Session Recording

Mezite records every SSH session, capturing the full terminal input and output stream. Recordings provide a complete audit trail of what happened during a session and can be replayed for incident review, compliance, or training.

Recording is enabled by default and requires no additional configuration. All SSH sessions are automatically recorded at the agent and uploaded to the auth service for centralized storage and playback.


How Recording Works

Session recording happens at the agent (the mezd process running on the target node), not at the proxy. This is an important architectural decision:

  • The proxy routes SSH connections between clients and agents via encrypted tunnels. It only sees the raw SSH protocol stream (key exchange, encrypted packets) — not the actual terminal content.
  • The agent terminates the SSH connection and allocates a PTY (pseudo-terminal) for the user's shell. It has direct access to the clean, decrypted terminal I/O — the commands typed and the output displayed.

By recording at the agent after SSH decryption and PTY allocation, Mezite captures exactly what the user sees in their terminal — clean text, not encrypted protocol bytes.

Recording Flow

  1. User connects via msh ssh. The proxy routes the connection through a reverse tunnel to the agent.
  2. The agent allocates a PTY and starts the user's shell. A recording stream is opened to the auth service via gRPC.
  3. As terminal I/O flows through the PTY, each chunk is written to a local recording file and simultaneously streamed to the auth service with millisecond-precision timestamps.
  4. When the session ends, the recording is finalized. The auth service stores it in the configured backend (local filesystem or S3) and links it to the session's audit events.

What Gets Recorded

Session recording captures terminal I/O — the byte stream between the user's terminal and the remote shell:

  • All commands typed by the user (stdin)
  • All output displayed in the terminal (stdout/stderr)
  • Terminal control sequences (colors, cursor movement, screen clears)
  • Timing information for each chunk (enabling real-time playback at any speed)

Recordings do not capture:

  • File transfer contents (SCP/SFTP payloads)
  • Port-forwarded traffic
  • Activity outside the Mezite-proxied session

Recording Modes

Mezite supports four recording modes, configured via the cluster setting session_recording:

Mode Where Upload Integrity
node-sync (cluster default) Agent Real-time streaming via gRPC Session killed if stream breaks
node (agent-binary default) Agent After session ends (bulk upload) Agent could tamper before upload
proxy Proxy Written by proxy (used for agentless OpenSSH MITM) Recording bypasses the agent
off - - No recording

Node-default vs cluster-default

Two layers can set the recording mode and they default to different values:

  • The mezd binary falls back to node (async) when neither the cluster nor the environment overrides it. This is what you get if an agent runs before it has talked to the auth service.
  • The cluster configuration is initialized to node-sync on first boot and can be changed with mezctl config set session_recording. The agent adopts the cluster value at startup and on every new session.
  • The per-agent MEZITE_RECORDING_MODE environment variable overrides both. Use this for one-off agents that must stay on async even when the cluster standard is sync (e.g. an agent behind a flaky link to the auth service).

For compliance-sensitive environments, leave the cluster setting on node-sync and do not override per-agent — that is the configuration that guarantees no session runs unrecorded.

node-sync

The agent records terminal I/O locally and streams each chunk to the auth service in real-time via gRPC. If the stream to the auth service breaks (network failure, auth service restart), the SSH session is terminated. This guarantees that every session is fully recorded — there is no window where a session runs unrecorded.

This is the recommended mode for compliance-sensitive environments where you need to guarantee that all session activity is captured.

node (Async)

The agent records terminal I/O to a local file. After the session ends, the recording is uploaded to the auth service via gRPC. The session continues even if the auth service is temporarily unreachable — the upload is retried later.

Use this mode when session availability is more important than recording guarantees (e.g., the agent must work even if the auth service is briefly down for maintenance).

Changing the Recording Mode

Set recording mode bash
# View current mode
mezctl config get session_recording

# Change to async mode
mezctl config set session_recording node

# Change back to real-time streaming
mezctl config set session_recording node-sync

# Disable recording entirely
mezctl config set session_recording off

The recording mode is a cluster-wide setting stored in the auth service. Agents query this setting at startup and when establishing new sessions. Changes take effect for new sessions — existing sessions continue with their original mode.


Storage

Session recordings are stored by the auth service. Two storage backends are available:

Local Filesystem (Default)

Recordings are stored on the auth service's local filesystem. This is suitable for single-node deployments and development.

Default local storage (no configuration needed) bash
# Recordings are stored in:
# /var/lib/mezite/recordings/<session-id>.rec

# To verify storage location:
mezctl config get session_recording
# node-sync

S3-Compatible Storage

For production deployments, configure an S3-compatible backend for durable, scalable recording storage. This works with AWS S3, MinIO, or any S3-compatible object store.

S3 storage configuration (environment variables) bash
# Set the storage backend to S3
MEZITE_RECORDING_BACKEND=s3

# S3 bucket and region
MEZITE_S3_BUCKET=mezite-session-recordings
MEZITE_S3_REGION=us-east-1

# Credentials (or use IAM roles if running on AWS)
MEZITE_S3_ACCESS_KEY=AKIAIOSFODNN7EXAMPLE
MEZITE_S3_SECRET_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

# Optional: prefix for S3 keys
MEZITE_S3_PREFIX=recordings/

# Optional: force path-style addressing (required for MinIO)
MEZITE_S3_FORCE_PATH_STYLE=true

# Optional: custom endpoint (for MinIO or other S3-compatible stores)
MEZITE_S3_ENDPOINT=http://minio:9000

Encryption at Rest

Recording encryption at rest is conditional. It is on only when recording_enc_key / MEZITE_RECORDING_ENC_KEY is set; if it is empty, recordings are written to the backend (local or s3) in the clear. Treat this as a deliberate operator choice, not a default.

When set, the value must be a hex-encoded 32-byte key. Each recording chunk is sealed with AES-256-GCM before being handed to the storage backend, so encryption covers both local-filesystem and S3 paths. The recording structure (timestamps, directions) remains parseable without the key — only the terminal-I/O payload is encrypted — which keeps playback indexing fast while still requiring the key to actually replay a session.

Rotating MEZITE_RECORDING_ENC_KEY orphans the previous recordings: their chunks can no longer be replayed without the old key. Keep the old value alongside the new one until the retention window for the old recordings has elapsed, then retire it.
Enable recording encryption bash
# Generate a 32-byte hex-encoded AES-256 key (one-time)
openssl rand -hex 32

# Wire it into the cluster
export MEZITE_RECORDING_ENC_KEY=3d992ae268214710f48d680749460...
mezhub --config=mezite.yaml

Playback

Use msh play to replay a recorded session in your terminal:

Session playback bash
# List recent sessions
msh sessions ls
# SESSION ID                            USER   NODE          LOGIN  STARTED               ENDED
# a1b2c3d4-e5f6-7890-abcd-ef1234567890  alice  web-server-01  ubuntu 2026-04-11T10:28:35Z  2026-04-11T10:41:09Z

# Play back a session
msh play a1b2c3d4-e5f6-7890-abcd-ef1234567890
# Playing session a1b2c3d4... (user: alice, host: web-server-01)
# ubuntu@web-server-01:~$ ls -la
# total 48
# drwxr-xr-x 6 ubuntu ubuntu 4096 Apr 11 10:28 .
# ...

# Play at 2x speed
msh play --speed=2 a1b2c3d4-e5f6-7890-abcd-ef1234567890

Audit Integration

Session recordings are linked to the corresponding session.start and session.end audit events. Each audit event includes the session ID, user, node, login, and duration — the recording adds the full terminal I/O for that session.

Find and play sessions from audit events bash
# List recorded sessions (admins see all; non-admins see their own)
msh sessions ls

# Filter recordings by user via the admin CLI
mezctl recordings ls --user alice

# List session.end events from the audit log
mezctl audit ls --type=session.end --since=24h

Permissions

Access to session recordings is controlled by RBAC:

  • Users can view recordings of their own sessions.
  • Admins can view all recordings and manage recording settings.

Agent Configuration

The agent's recording behavior is controlled by the MEZITE_RECORDING_MODE environment variable. This is separate from the cluster-wide session_recording setting — the agent env var determines what the agent does, while the cluster setting determines the default for new deployments.

Agent recording environment variables bash
# Recording mode (default: "node")
# The cluster default is "node-sync" but the agent binary defaults to "node".
# Set explicitly to match your cluster's session_recording setting.
MEZITE_RECORDING_MODE=node-sync

# Enhanced command-capture stream (Linux only)
# When on, the agent emits a structured per-command stream alongside the
# full terminal-I/O recording so compliance queries can answer "did X run"
# without scanning the full recording. The env var is named MEZITE_BPF_ENABLED
# for forward compatibility, but the current implementation polls the
# session shell's process tree under /proc — it is not true eBPF today.
# Very short-lived commands (sub-100ms) may be missed by the poll. The
# full terminal recording always captures everything regardless of this flag.
MEZITE_BPF_ENABLED=true

Agentless Nodes

Agentless OpenSSH nodes (registered with mezctl nodes add --openssh) are recorded via SSH MITM. The proxy terminates the client's SSH session and opens a separate SSH connection to the target host using a short-lived certificate signed by the Mezite User CA. The proxy sits between two decrypted SSH sessions and records the plaintext channel data — exactly what the user types and sees.

This requires no additional configuration beyond the standard agentless setup (TrustedUserCAKeys on the target host). Recordings appear in msh sessions ls and can be played back with msh play just like agent-recorded sessions.


Current Status

Feature Status
Agent-side PTY recordingAvailable
Real-time streaming (node-sync)Available
Async upload (node)Available
Local filesystem storageAvailable
S3-compatible storageAvailable
AES-256-GCM encryption at restAvailable
msh play CLI playbackAvailable
Command-capture recording (Linux, /proc-based)Available
Agentless node recording (SSH MITM)Available
Web UI session playerPlanned

Next Steps