""" Session-Aufzeichnung mit Hash-Verkettung (siehe Konzept 6.5). Jede Session schreibt eine eigene JSONL-Datei unter settings.recordings_dir. Jede Zeile verkettet sich mit der vorherigen (gleiches Prinzip wie das Audit-Log, app/security/audit.py), damit nachtraegliche Manipulation der Aufzeichnung erkennbar ist. """ from __future__ import annotations import hashlib import json import time from pathlib import Path from app.config import settings GENESIS_HASH = "0" * 64 class SessionRecorder: def __init__(self, session_id: int) -> None: self.session_id = session_id self.path = settings.recordings_dir / f"session_{session_id}.jsonl" self._prev_hash = GENESIS_HASH self._start_ts = time.time() self._fh = open(self.path, "a", encoding="utf-8") try: self.path.chmod(0o600) except OSError: pass def record(self, direction: str, data: str) -> None: """direction: 'input' (Tastatureingabe) oder 'output' (Terminal-/RDP-Ausgabe).""" offset = round(time.time() - self._start_ts, 4) entry = {"t": offset, "dir": direction, "data": data} entry_json = json.dumps(entry, ensure_ascii=False, sort_keys=True) entry_hash = hashlib.sha256((self._prev_hash + "|" + entry_json).encode()).hexdigest() line = json.dumps({"entry": entry, "prev_hash": self._prev_hash, "hash": entry_hash}) self._fh.write(line + "\n") self._fh.flush() self._prev_hash = entry_hash def close(self) -> None: if not self._fh.closed: self._fh.close() def verify_recording(path: Path) -> bool: prev_hash = GENESIS_HASH with open(path, encoding="utf-8") as fh: for line in fh: if not line.strip(): continue row = json.loads(line) if row["prev_hash"] != prev_hash: return False entry_json = json.dumps(row["entry"], ensure_ascii=False, sort_keys=True) expected = hashlib.sha256((prev_hash + "|" + entry_json).encode()).hexdigest() if expected != row["hash"]: return False prev_hash = row["hash"] return True