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
shelltrapdruns as root; the worker runs asshelltrap-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 (
/homeby 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
MemAvailableremaining 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
clamdand a Unix socket reachable by theshelltrap-scanuser,/run/clamav/clamd.ctlby default. A socket readable only by root is not enough. If the service or the worker’s access is unavailable, the state becomesdegradedand neverclean. - 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 eithercurlorwgetfor the host installer;unzipin 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
--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:
- Full —
clamdis running or installable, the configured socket is genuinely reachable byshelltrap-scan, and at least 1.5 GiBMemAvailableremains 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
| Path | What it is |
|---|---|
/etc/shelltrap/shelltrap.toml | configuration (a conffile; survives upgrades) |
/var/lib/shelltrap/state.db, feeds/, cache/, quarantine/ | persistent state |
/run/shelltrap/api.sock, /run/shelltrap/upload.sock | local sockets |
/usr/lib/shelltrap/php/shelltrap-prepend.php | the upload adapter |
/etc/shelltrap/upload-policy.json | atomic 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.
# 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
- Does a matching
shelltrap.iniexist for every lsphp ABI, and doesauto_prepend_filepoint at/usr/lib/shelltrap/php/shelltrap-prepend.php? - Does
/run/shelltrap/upload.sockexist, and are the policy file and theDOCUMENT_ROOTprefix valid? - On
denythe adapter must return HTTP 403 and delete the temporary file; withon_error = "closed"expect HTTP 503. Withopenthe application continues, but the error must remain diagnosable.
Next
Abridged from the Shelltrap installation manual, revision 2026-09-04.