01 / 10
▶ session recording  ·  mTLS  ·  mandatory acknowledgement
SUDO-LOGGER
Real-time sudo session recording with mandatory remote acknowledgement.
If the log server goes dark — so does your terminal.
$ sudo-shipper -server logserver:9876 

THE
BLIND
SPOT

Every sudo command grants root. Without verified remote logging, you have no tamper-proof record of what actually happened — or if it happened at all.

Traditional syslog is advisory. The admin can stop the logger, the log server can be unreachable, or the service can simply be killed. None of these edge cases should allow privileged execution to go unrecorded.

sudo audit log — /var/log/sudoreplay
09:14:02aluncat /etc/shadow
09:18:41alunsystemctl restart nginx
09:31:07alunbash
09:31:22alunrm -rf /var/log/*
09:44:12alunls /root
HOW IT WORKS
C Plugin
PLUGIN
Loaded by sudo for every invocation.
Captures all I/O streams.
Blocks sudo entirely if shipper is unreachable at start.
Unix socket
Go Daemon
SHIPPER
Local bridge running as root.
ACK state per session.
Heartbeat every 400 ms.
Mutual TLS · 9876
Go Server
LOGSERVER
Central log receiver.
ed25519-signed ACKs per chunk.
sudoreplay-compatible storage.
If the server is unreachable when sudo runs → the command is blocked before execution. No server, no sudo.
THE FREEZE MECHANISM
[ SUDO-LOGGER: log server unreachable — input frozen ]
Waiting for log server to come back...
🟢
Running
Session active. Server ACKs every chunk. Heartbeat every 400 ms.
📡
Network lost
No HEARTBEAT_ACK within 800 ms. Connection declared dead.
❄️
Frozen
cgroup.freeze=1 suspends the session. Banner shown on tty. No input reaches the shell.
Resumed
Network returns. ACK received. cgroup unfreezes. Session continues seamlessly.
Freeze latency: ~1 second  ·  Auto-recovery: < 2 seconds  ·  GUI apps frozen via SIGSTOP (pgid-aware)  ·  Ctrl+C always works
SECURITY PROPERTIES
🚫
Blocked at start
If the log server is unreachable when sudo runs, the session is rejected before the command executes. No logging capability = no execution.
🔐
Mutual TLS
Both client and server authenticate with certificates from a shared CA. Unknown clients are rejected at the TLS handshake.
ed25519-signed ACKs
The server signs every ACK with an ed25519 private key. Clients hold only the public key — a compromised client cannot forge ACKs fleet-wide.
🗄
Tamper-evident storage
Logs are written on a separate server the sudo-running user has no access to. Users cannot modify their own audit trail.
Session terminated if shipper is killed
If the shipper socket drops mid-session (EPIPE / ECONNRESET), the plugin sends SIGTERM to sudo within 150 ms. The attacker's shell is terminated — they cannot continue unlogged. The kill command itself is already in the log.
⚠️
Incomplete session detection
If the shipper is killed mid-session, the server writes an INCOMPLETE marker and logs a SECURITY: warning. The replay UI flags the session with a red border and warning badge.
Complete I/O capture — replayed with sudoreplay
stdin · stdout · stderr · tty input · tty output — all recorded in real time with full timing data. Replay any session with sudoreplay -d /var/log/sudoreplay <TSID>.
HEARTBEAT & ACK PROTOCOL
PLUGIN
C · per sudo pid
Unix socket
SHIPPER
Go · local daemon
mutual TLS · 9876
LOGSERVER
Go · remote
plugin → shipper → server
SESSION_START / tty_output (seq=N)
chunk + sequence number
server → shipper → plugin
ACK (seq=N · 64-byte ed25519 sig)
server signs; plugin verifies
shipper → server   every 400 ms
HEARTBEAT
liveness probe
server → shipper
HEARTBEAT_ACK
missing > 800 ms → freeze
400 ms
heartbeat interval
800 ms
dead threshold → cgroup.freeze
150 ms
plugin monitor poll · socket drop → SIGTERM
CRYPTOGRAPHY
🔐   Mutual TLS (mTLS)
A private CA issues certificates for both server and all clients.
TLS handshake requires a valid client cert — unknown shippers are rejected before any data is exchanged.
Client certificate CN is verified against the host field in SESSION_START — a compromised shipper on host A cannot forge logs for host B.
All session I/O is encrypted in transit — no plaintext on the wire.
server cert CN=sudo-logserver ← signed by shared CA
client cert CN=<hostname> ← signed by shared CA
cert CN verified vs SESSION_START host field
✍   ed25519 ACK Signing
Server holds the ed25519 private key — never distributed to clients.
Each ACK is signed over a payload binding session ID, sequence number, and nanosecond timestamp — prevents replay of old ACKs.
Shipper holds only the public key (ack-verify.key): can verify but cannot forge.
Even a fully compromised shipper cannot generate valid ACKs fleet-wide.
ACK payload signed by server
  session_id variable ← ties ACK to session
  seq 8 bytes ← prevents replay of old ACK
  timestamp_ns 8 bytes ← freshness
  signature 64 bytes ← ed25519 over above
CGROUP FREEZE — INTERNALS
# shipper creates one cgroup per session
/sys/fs/cgroup/
  sudo_logger/<session_id>/
    cgroup.procs PID # sudo pid; children inherit

# suspends every task in the cgroup instantly
cgroup.freeze = 1
# resumes all tasks instantly
cgroup.freeze = 0
Terminal (bash, vi, …)
Stays in cgroup — cgroup.freeze=1 is enough.
Plugin already blocks TTY input, SIGSTOP not needed (would trigger job control → bash to background).
GUI apps (gvim, okular, …)
systemd/GNOME moves them to app-*.scope — outside our cgroup. No controlling TTY → shipper sends SIGSTOP directly (freeze, not kill). On resume: SIGCONT.
① session starts
Shipper creates cgroup and adds the sudo PID. All child processes inherit the cgroup. Shipper polls every 10 ms to catch GUI apps that systemd moves out.
② normal operation
I/O chunks flow, server replies with signed ACKs. Heartbeat every 400 ms.
③ ACK timeout — 800 ms
Shipper writes 1 to cgroup.freeze — kernel suspends all tasks. GUI apps outside the cgroup receive SIGSTOP. Banner shown on /dev/tty.
④ frozen — input blocked
No keystrokes reach the shell. Ctrl+C / Ctrl+Z still work — monitor thread reclaims terminal foreground group every 150 ms.
⑤ network returns
ACK received → cgroup.freeze=0, SIGCONT to any SIGSTOP'd processes. Session resumes with no data loss.
WEB REPLAY INTERFACE

BROWSER
PLAYBACK

A self-contained HTTP server reads the same iolog directories as sudoreplay and serves a full terminal player — no database, no dependencies.

  • Risk scoring 0–100 — configurable YAML rules
  • Summary tab — per-user stats, sortable, filterable
  • Anomalies tab — incomplete, high-risk, root shell, after-hours
  • Settings tab — edit risk rules in the browser
  • Full command + arguments in session list
  • Live search by user, host, or command
  • Play / pause · seek · speed 0.25×–16×
  • Keyboard: Space · ← / → · R
  • xterm.js — pixel-accurate terminal replay
  • Single binary with embedded frontend
$ dnf install sudo-logger-replay-1.10.0-1.fc43.x86_64.rpm
$ systemctl enable --now sudo-replay
→ http://localhost:8080
sudo-replay web interface

READY TO
DEPLOY

▸ RPM · systemd · mTLS · sudoreplay
Full replay with native sudo iolog format
Real-time streaming, zero local buffering
Freeze within ~1 s of network loss
Automatic recovery when network returns
GUI app freeze via SIGSTOP (pgid-aware)
RPM packages with systemd integration
Risk scoring — YAML rules, 0–100 per session
Summary / Anomalies / Settings tabs
Browser replay — full command + args
Session terminated if shipper killed (<150 ms)
sudo 1.9+ · Fedora / RHEL
github.com/alun-hub/sudo-logger