Skip to main content

Browser SSH Session Recording

Status

Implemented. Branch feature/ssh-session-recording. Closes the recording deferral noted in OP-086.

  • Taskboard OP-D089; client requirement "Browser SSH: recording, command logging, JIT, RBAC, timeouts" (VN-02).

Problem Statement

The browser-SSH broker stored recording metadata only: recording_ref pointed nowhere real and recording_sha256 was a hash of session_id:command_count:ended_at — not of an actual recorded stream. So "session recording" was over-claimed.

Architectural Intent

The gateway already terminates SSH server-side and relays the PTY byte stream, so the honest place to capture a recording is the relay itself. Capture the output stream there, persist it at session end with a real content hash, and expose a tenant-scoped retrieval endpoint. No agent or client change.

What Was Implemented

  • SshTerminator captures the PTY output stream (_pump_to_browser) into a buffer — a faithful server-side transcript (the PTY echoes keystrokes into output).
  • On gateway teardown, end_ssh_session_from_gateway(..., recording=...) persists the transcript (base64) to a new ssh_session_recordings table and sets recording_sha256 to the real sha256 of the captured bytes.
  • GET /api/v1/rmm/ssh-sessions/{session_id}/recording returns the transcript + hash to the operator who owns the session, or an auditor/tenant-admin/system-admin.

Components Involved

  • poc/ingest_api/ssh_terminator.py (capture), ssh_gateway.py (teardown handoff)
  • poc/ingest_api/ssh_broker_service.py (_persist_ssh_recording, read_ssh_recording)
  • db/postgres/021_ssh_session_recordings.sql + sqlite init_db + RLS
  • poc/ingest_api/http_routes.py, specs/openapi.yaml

APIs / Events / Schemas

  • New: GET /api/v1/rmm/ssh-sessions/{session_id}/recording (OpenAPI updated).
  • New table ssh_session_recordings (RLS, tenant-scoped). No event-schema change; the session.ssh.ended payload now carries a real recording_sha256.

Deployment Notes

Run Postgres migration 021_ssh_session_recordings.sql (wired into the Makefile and compose db-migrate). No agent change.

Security / Tenant Isolation

Recording rows are tenant-scoped (RLS); retrieval requires read role and, for an operator, ownership of the session. Auditors may read (read-only evidence access).

Validation Steps

API Validation

# after a browser SSH session ends:
GET /api/v1/rmm/ssh-sessions/{session_id}/recording
# -> { recording_sha256, byte_len, transcript_b64 };
# base64-decode transcript_b64, sha256 it, and confirm it equals recording_sha256

Smoke / Tests

tests/test_ssh_session_recording.py: capture→persist→real hash, content-not-metadata, tenant isolation + operator-owner scope + auditor read.

Known Limitations

  • Captures the output stream (server-side typescript), not a timed asciicast replay.
  • Transcript is stored in the tenant-scoped table in the clear (POC); envelope encryption via the per-tenant key (VN-12) is the contract-designed follow-up.

Follow-Up Work

  • xterm replay UI; timed/asciicast capture; envelope encryption at rest.

Acceptance Criteria Mapping

OP-D089 / VN-02 "recording" — a browser SSH session is recorded server-side and retrievable with a content hash that verifies the actual session. Satisfied by the capture + persistence + retrieval above and the four DoD tests.