Security model
What Ghost Shell protects and what it does not - trust boundary, encryption, key immutability, fail-open semantics, and the integrity caveat.
Read this before relying on Ghost Shell for audit. It explains what Ghost Shell does — and, just as important, what it does not protect against.
Integrity caveat
Warning: Ghost Shell is not tamper-proof against a user who simply avoids it. It provides root-only access to recordings plus live tail, but a malicious user with shell access can run a shell that was never wrapped by
ghostshell— the auto-record hook and the sshd ForceCommand wrapper are both deliberately fail-open and can be sidestepped. Non-circumventable capture requires PAM- or kernel-stage hooks, which this project does not implement. Treat Ghost Shell as an audit and visibility tool for cooperative environments, not as an enforcement boundary against a determined adversary on the box.
This is the flip side of fail-open: availability is prioritized over guaranteed capture.
Trust boundary
- The central store is
/var/lib/ghostshell,root:root 0700; recordings areroot:root 0600. Only root can read central recordings — users cannot read even their own. - The recorder reaches the daemon over
/run/ghostshell-daemon.sock. The socket mode is0666— any user can connect to submit their own session — but the daemon authenticates the peer withSO_PEERCREDand attributes every session to the kernel-reported UID. A user cannot forge another user's identity or read data back over the socket. Privacy is enforced by filesystem permissions on the store, not by the socket mode. - All read commands (
play,tail,tree,search,export,ls --all/--user,ansible) require root, because they read the root-only store directly. The audit added an explicit euid check to every one of them as defense-in-depth, so relocating the store to a looser path does not silently open it.
Encryption at rest and key immutability
- Central recordings are AES-256-GCM encrypted; on disk a
.castis opaque tocat/strings/grep. Framing details are on the file format page. - The key (
/var/lib/ghostshell/.ghostshell.key,root:root 0600) is unique per server and made immutable withchattr +i: it cannot be removed, renamed, or rewritten byrm/vi/sed/>/tee— even by root — until someone explicitly runschattr -i. - Immutability depends on the filesystem. ext4/xfs support the attribute; overlayfs, tmpfs, and many network mounts silently ignore it, leaving the key deletable. The daemon reads the flag back after setting it and, if it did not stick, logs
WARNING: ... the at-rest key is NOT protected from deletion/modification. If you see that line, move the store to ext4/xfs or accept the reduced guarantee. (The deployment review reproduced this empirically on overlayfs and tmpfs.) - Back up the key. If it is lost, every encrypted recording is permanently unreadable. The daemon refuses to start if the key is missing while encrypted recordings exist. See backup and prune.
Optional playback password
An optional playback password (hashed in /etc/ghostshell/.playback_passwd, root:root 0600) gates every content- or metadata-revealing command — play, tail, tree, search, export, and the central ls views. A separate prune password gates deletion. This is a second factor on top of root, not a replacement for filesystem permissions. Manage it with ghostshell init --reset-password / --clear-password (both require the current password).
Path traversal and permission posture
- Session IDs are validated and resolved within the central store; the ingest sweep admits only regular
.castfiles and rejects symlinks and irregular entries, so a crafted home-directory entry cannot redirect the root-owned ingest at an arbitrary target. --from/--todate strings go todate -das plain argv, never through a shell — command substitution and separators are inert.- The daemon writes recordings
0600, store directories0700, and runs under a hardened systemd unit:NoNewPrivileges,ProtectSystem=full,SystemCallFilter=@system-service,ProtectKernelModules/ProtectKernelLogs,LockPersonality,RestrictNamespaces/RestrictRealtime,ProtectClock/ProtectHostname,RestrictAddressFamilies=AF_UNIX,UMask=0077.systemd-analyze securityrates it 3.9 OK (measured on real systemd 255, improved from 7.0 MEDIUM). - The unit intentionally does not enable
ProtectHome: the daemon must read and delete each user's~/.local/share/ghostshellspool during ingest.
Fail-open semantics
Every capture path is fail-open by design, so Ghost Shell can never lock a user out or break a login or SSH session:
- If the daemon is down, rejects the session (per-user
session_capreached, store full, ID collision), or is wedged,ghostshell recwrites a user-local file (~/.local/share/ghostshell,0600); the daemon ingests it on next startup. The daemon sends anOKacknowledgement only once a session is registered, and the recorder waits for it with a bounded timeout — a rejection or a hung daemon can never silently swallow a recording, nor block a login. - If the central store fills mid-session, the daemon logs a
DISK FULLerror naming the path, terminates that session cleanly, and keeps serving other users. - The
profile.dhook and the sshdForceCommandwrapper fall through to a normal shell on any error, and passscp/sftp/rsync/git transfers through untouched.
Audited in the open
The claims above are not self-asserted. A full technical audit (AUDIT.md, ~13.2 KLOC across 16 packages) fixed 23 of 24 actionable findings in-tree, and the deployment review (DEPLOYMENT.md) validated the built .deb empirically on a real Ubuntu 24.04 install: cross-user read denial, symlink-attack rejection (an evil.cast → /etc/shadow symlink was refused with zero bytes leaked), disk-full survival, fail-open fallback and re-ingest, and race-detector-clean concurrency. Both documents ship in the repository.
Questions like "can root read my session?" are answered directly in the threat model FAQ.