Skip to content
ghostshell

SSH recording

Opt in to recording non-interactive SSH commands with the sshd ForceCommand wrapper - validated, reversible, and fail-open.

The login hook records interactive sessions only. To also record non-interactive SSH commands (ssh host "cmd"), enable the sshd ForceCommand wrapper.

Note: This is opt-in and NOT installed by default. The package ships the wrapper binary but leaves it inactive, so an unattended server is never silently reconfigured to wrap every SSH login.

Enable

bash
sudo ghostshell init --enable-ssh-forcecommand

This:

  1. Writes /etc/ssh/sshd_config.d/zz-ghostshell.conf — a ForceCommand /usr/libexec/ghostshell-ssh-wrap drop-in
  2. Ensures the main sshd_config has an Include /etc/ssh/sshd_config.d/*.conf line
  3. Validates with sshd -t and reloads sshd

If validation fails, it reverts only the edits it made — never touching a pre-existing Include — so sshd is always left parseable. It is idempotent: re-running when already enabled changes nothing.

Disable

bash
sudo ghostshell init --disable-ssh-forcecommand

This removes the drop-in and reloads sshd, leaving the Include directive and any other drop-ins untouched.

What passes through

  • scp / sftp / rsync / git transfers pass through untouched — file transfers are never wrapped in a recording.
  • Interactive logins keep recording via the profile.d hook; there is no double-wrap.
  • Fail-open: if anything is off, the command runs normally. SSH is never blocked.

Exclude an account

Edit the generated drop-in to use a Match block instead of the global ForceCommand, then validate and reload:

text
Match User *,!adminuser
    ForceCommand /usr/libexec/ghostshell-ssh-wrap
bash
sudo sshd -t && sudo systemctl reload ssh

Warning: Like the login hook, the wrapper is deliberately fail-open and can be sidestepped. See the integrity caveat.

Managed hosts running Ansible receive raw SSH execs — the wrapper captures them if enabled, but without task names or status. For structured playbook records, see Ansible tracking.

Source of truth: README on GitHub