shelltrap.com
en de

Documentation

Installation and first run

The installer probes what the host can actually do rather than trusting a kernel version. Read the profile section and the report-only section before you run anything.

Requirements

  • Root access, or a package service account with full installation rights. The broker shelltrapd runs as root; the worker runs as shelltrap-scan, a system user without a login shell and without a home directory.
  • A supported CyberPanel operating system: Ubuntu 20.04, 22.04 or 24.04; AlmaLinux, RockyLinux or RHEL 8, 9 or 10; CloudLinux 8; CentOS 9. Debian is only third-party supported by CyberPanel and is not promised.
  • A Linux filesystem under the configured customer roots (/home by default), enough memory, and a kernel on which the installer can actually probe the fanotify and file-handle capabilities.
  • For the Full profile: at least 1.5 GiB of MemAvailable remaining after the measured reload reserve, which includes the observed clamd footprint plus a fixed safety margin. This measurement must not be replaced by a global sysctl change.
  • For ClamAV in the Full profile: a running or installable clamd and a Unix socket reachable by the shelltrap-scan user, /run/clamav/clamd.ctl by default. A socket readable only by root is not enough. If the service or the worker’s access is unavailable, the state becomes degraded and never clean.
  • For the PHP upload adapter: a supported lsphp/LSWS installation. The adapter needs only the local socket /run/shelltrap/upload.sock.
  • Tools: sha256sum, GnuPG and either curl or wget for the host installer; unzip in addition for the CyberPanel package. The native package manager resolves dependencies.

Host preflight

Before any download, the host installer prints read-only facts about the machine and changes nothing:

shelltrap: kernel=6.8.0-… CAP_SYS_ADMIN=absent provisional_tier=unknown (requires shelltrapd --check)
shelltrap: SELinux=enforcing AppArmor=enabled LSWS=OLS
shelltrap: mount_probe=/home mount_target=/home mount_fs=xfs mount_options=rw,relatime

SELinux may read enforcing, permissive, disabled or unknown; AppArmor is reported as enabled, disabled or unknown. LSWS=OLS comes from the CyberPanel pattern /usr/local/lsws/bin/openlitespeed; with the Enterprise control binary instead it reads enterprise, and with neither marker it stays unknown. Mount facts come from findmnt; if that is missing, all three fields say unknown rather than guessing.

An unknown is a diagnosis, not a quiet promise of tier A. The authoritative statement is always the post-installation call:

shelltrapd --config /etc/shelltrap/shelltrap.toml --check

Platform and degradation tiers

Four watcher tiers, chosen from runtime capability rather than kernel versionThe installer probes the host's capabilities. Tier A uses fanotify with file handles and gives full real time. Tier B works without file handles, so renames arrive late. Tier C runs without CAP_SYS_ADMIN using budgeted inotify plus a crawler. Tier D does not install and only reports diagnostics.RUNTIME PROBE, NOT A VERSION ASSUMPTIONAfullfanotify with FAN_REPORT_DFID_NAME, marks per mountUbuntu 22.04/24.04 · Alma/Rocky/RHEL 9 and 10real time, every featureBfd modefanotify without file handles, renames via ctime reconciliationAlma/Rocky/RHEL/CloudLinux 8 · Ubuntu 20.04 GA kernelreal time on write, renames delayedCcontainerbudgeted inotify plus a checkpointing crawlerunprivileged LXC/OpenVZ without CAP_SYS_ADMINdelayed detection, stated in the productDunsupportedno usable backend or an unknown filesystemeverything elseno installation, diagnostics only
Watcher tier and scanner profile are separate axes: tier A does not automatically mean there is enough memory for the ClamAV full profile.

--check returns JSON with tier, diagnostic.tier and, per mount, reason and error. When a backend is configured explicitly, the top-level tier reflects that choice — for a real capability statement, read diagnostic.tier as well.

Verified installation

Release artefacts are built with make dist, and each of the four package files gets a .sha256 and a detached ASCII-armored .asc signature. Before publication these files must exist per version:

shelltrap_<version>_<arch>.deb                  + .sha256 + .asc
shelltrap-cyberpanel_<version>_all.deb          + .sha256 + .asc
shelltrap-<version>-1.<arch>.rpm                + .sha256 + .asc
shelltrap-cyberpanel-<version>-1.noarch.rpm     + .sha256 + .asc

From the repository, letting the package manager verify the index signature:

sudo apt-get update && sudo apt-get install shelltrap shelltrap-cyberpanel
# or
sudo dnf install shelltrap shelltrap-cyberpanel

From local files, verifying first:

gpgv --keyring /usr/share/keyrings/shelltrap-archive.gpg \
  dist/shelltrap_1.0.0_amd64.deb.asc dist/shelltrap_1.0.0_amd64.deb
dpkg-deb --info dist/shelltrap_1.0.0_amd64.deb
sudo apt-get install ./dist/shelltrap_1.0.0_amd64.deb

The host installer at scripts/install.sh — also shipped as /usr/share/shelltrap/install.sh — downloads over HTTPS, checks SHA-256 and the detached signature, then hands over to the native package manager. The curl path allows only HTTPS redirects; the wget fallback refuses redirects entirely. The script must exist as a file and does not support curl | bash.

Full and Lite profile

The preflight chooses only after measuring:

  • Fullclamd is running or installable, the configured socket is genuinely reachable by shelltrap-scan, and at least 1.5 GiB MemAvailable remains after the reload peak reserve. ClamAV, YARA, hash and heuristic engines are all in play.
  • Lite — those conditions are not met. The configuration records scanner.enable_clamd = false, coverage is lower, and ClamAV findings cannot be promised.

A clamd started later does not silently upgrade the host to Full. Check the decision:

grep -n 'enable_clamd' /etc/shelltrap/shelltrap.toml
shelltrapd --config /etc/shelltrap/shelltrap.toml --check

Paths, units and the PHP adapter

PathWhat it is
/etc/shelltrap/shelltrap.tomlconfiguration (a conffile; survives upgrades)
/var/lib/shelltrap/state.db, feeds/, cache/, quarantine/persistent state
/run/shelltrap/api.sock, /run/shelltrap/upload.socklocal sockets
/usr/lib/shelltrap/php/shelltrap-prepend.phpthe upload adapter
/etc/shelltrap/upload-policy.jsonatomic policy mirror for the adapter
/var/log/shelltrap/text and audit logs

Directory modes under /var/lib/shelltrap are staggered on purpose: the parent is 0711, feeds/ is 0755 with files 0644 so the unprivileged worker can read rule generations, while quarantine/, cache/ and state.db stay root-only (0700 / 0600).

The package ships shelltrapd.service, shelltrap-scan.service with a timer and shelltrap-feeds.service with a timer. Put your own limits in a drop-in, never in the vendor unit:

sudo install -d /etc/systemd/system/shelltrapd.service.d
sudo systemctl daemon-reload
sudo systemctl restart shelltrapd.service

The vendor unit restricts RestrictAddressFamilies to AF_UNIX deliberately. Network access for SMTP or webhook targets is a separate, deliberately installed drop-in shipped as an example under /usr/share/shelltrap/systemd/. NoNewPrivileges=yes is set; RestrictSUIDSGID=no is a narrow, deliberate exception, because restore has to put original setuid and setgid modes back with fchmod.

For each lsphp version present, the package writes an ini file containing:

auto_prepend_file=/usr/lib/shelltrap/php/shelltrap-prepend.php

and then touches the CyberPanel marker /usr/local/lsws/admin/tmp/.lsphp_restart.txt before attempting a graceful restart with systemctl restart lsws or the Enterprise lswsctrl restart. killall -9 lsphp is not permitted. If the marker cannot be written or the graceful restart fails, activation aborts with an error rather than leaving PHP in an unclear state.

Opt-out: setting SHELLTRAP_SKIP_PHP_ADAPTER=1 in the environment of the package installation skips activation entirely — no ini change, no LiteSpeed restart. This is the mode for a report-only test stage; you can activate later by re-running the package configuration without the variable (dpkg-reconfigure shelltrap, or dnf reinstall shelltrap).

Start report-only on a host with customers

Package installation starts the service immediately. To make sure it never runs with the default actions on a production host, set the policy first and take the adapter out of the picture:

SHELLTRAP_SKIP_PHP_ADAPTER=1 apt-get install -y ./dist/shelltrap_<version>_amd64.deb
shelltrap policy set global signature.action=report hash.action=report \
  heuristics.action=report upload.enabled=false
shelltrap policy get global

A systemd drop-in with CPUQuota, IOWeight, Nice and MemoryHigh before the first start is worth the two minutes it costs: a first start without a clean shutdown marker plans a full reconciliation crawl, and on a busy host that is real work. On our own reference host that crawl ran at roughly 114 scans per minute with two workers and about 270 with four.

root@web1 — /root
# start in report-only: record everything, move nothing
$ shelltrap policy set global signature.action=report hash.action=report heuristics.action=report upload.enabled=false
policy generation 18
$ shelltrap --json findings list --verdict malicious --page-size 1
{"items":[{"id":"f_01J9…","path":"…/wp-content/uploads/2026/03/thumb-cache.php",
  "verdict":"malicious","confidence":0.97,"action":"report",
  "signals":[{"engine":"heuristics","id":"php.dynamic_eval","score":45},
             {"engine":"yara","name":"WEBSHELL_PHP_Generic","author":"…","license":"DRL-1.1"}]}],
 "total":1,"page":1,"page_size":1}
# nothing was quarantined: the policy said report, so the policy won

The policy that records everything and moves nothing, and the finding it produced.

Then verify:

shelltrap health
shelltrap status
stat -c '%a %U:%G %n' /run/shelltrap/api.sock /run/shelltrap/upload.sock
ps -o pid,user,stat,cmd -C shelltrap-scanner

Expect tier A, a healthy store, two workers running as shelltrap-scan, Landlock active, a site index matching your CyberPanel database, and an active feed generation. Without a feed generation only ClamAV (in the Full profile) and the heuristics run, which is acceptable for a test stage but is reported as degraded rather than glossed over.

Upgrade, rollback and repair

sudo apt-get update && sudo apt-get install --only-upgrade shelltrap
sudo dnf upgrade shelltrap

Back up shelltrap feeds list, the configuration and the audit and quarantine data first. Feed generations can be rolled back through the CLI while the broker is running:

shelltrap feeds list
shelltrap feeds rollback GENERATION

Because shelltrap.toml is a conffile, an upgrade on a host with a modified configuration should be run as:

apt-get -o Dpkg::Options::=--force-confold install ./dist/shelltrap_<version>_amd64.deb

Otherwise the package can be left in install ok unpacked while the old daemon keeps running — and a mixed version is worse than either: an old daemon starting new worker binaries hits a strict protocol decoder that rejects unknown fields, and workers die until the restart.

After a CyberPanel upgrade, re-run the packaged repair path:

sudo /usr/share/shelltrap-cyberpanel/install.sh

The broker and the CLI must keep working even when the panel UI is broken. If the panel mechanics or the expected plugin archive structure differ from what the repair expects, that is an open integration issue and must not be “fixed” by patching lines on suspicion.

Removal and the purge gate

A normal removal stops the services and removes the program files. Configuration, logs, state and quarantine survive:

sudo apt-get remove shelltrap-cyberpanel shelltrap
sudo dnf remove shelltrap-cyberpanel shelltrap

purge removes configuration and logs. Quarantine is evidence and is only removed behind an explicit environment gate:

sudo SHELLTRAP_PURGE_QUARANTINE=1 apt-get purge shelltrap

RPM has no package-manager purge step. For a deliberate cleanup, call the packaged maintainer path with the gate before the erase:

sudo env SHELLTRAP_PURGE_QUARANTINE=1 /usr/libexec/shelltrap/maintainer/postrm purge
sudo dnf remove shelltrap-cyberpanel shelltrap

The purge helper opens /var/lib/shelltrap as a parent descriptor, verifies the root-owned quarantine entry, renames it atomically to a staging name and removes contents only relative to descriptors, comparing Linux mount IDs from /proc/self/fdinfo/<fd> at every level. Unsafe types or identity races abort safely and leave the staging data for manual inspection. Symlinks and hard links are unlinked as directory entries and never followed.

Troubleshooting

Tier D or an unexpected degradation

shelltrapd --config /etc/shelltrap/shelltrap.toml --check
grep '^CapEff:' /proc/self/status

Read diagnostic.mounts[].capabilities.reason and .error — typically permission_denied, not_supported or not_found. Do not blame kernel versions by reflex: a missing CAP_SYS_ADMIN, an overlay or network filesystem, or non-exportable file handles are the common causes.

clamd and the Full profile

test -S /run/clamav/clamd.ctl
systemctl status clamav-daemon.service clamd.service
grep -nE 'enable_clamd|clamd_socket' /etc/shelltrap/shelltrap.toml

Socket permissions, the clamav user and the measured memory peak have to line up. On failure the result stays degraded; a restart must never reinterpret an earlier scan as clean.

The PHP adapter and uploads

  1. Does a matching shelltrap.ini exist for every lsphp ABI, and does auto_prepend_file point at /usr/lib/shelltrap/php/shelltrap-prepend.php?
  2. Does /run/shelltrap/upload.sock exist, and are the policy file and the DOCUMENT_ROOT prefix valid?
  3. On deny the adapter must return HTTP 403 and delete the temporary file; with on_error = "closed" expect HTTP 503. With open the application continues, but the error must remain diagnosable.

Next

Abridged from the Shelltrap installation manual, revision 2026-09-04.