shelltrap.com
en de

Documentation

Configuration and policies

Almost everything that changes behaviour is a policy with three layers — global, account, domain — and the more specific layer wins.

The policy model

Every setting that steers behaviour is a policy on three levels:

  1. global — the whole host
  2. account — one CyberPanel user
  3. domain — one website or child domain

The more specific level wins, and each level may inherit or override individual keys. Inside a domain there can also be path rules using globs relative to the document root.

Assignment works through a document-root index the broker keeps from the CyberPanel database (website, child domain, document root, owner), refreshed on a timer and on panel changes. fanotify events are mapped to an account and a domain by the longest matching document-root prefix. The PHP adapter passes DOCUMENT_ROOT and SERVER_NAME; the document root decides and the server name is only used as a plausibility check. Files outside every document root — mail, backups, the home root — fall under the account policy.

Policies are versioned. A change invalidates only the path-dependent policy verdicts of the affected domain, not content verdicts.

The keys

KeyWhat it doesDefault
upload.enabledwhether the synchronous upload gate is consultedon
upload.on_erroropen or closed when the gate cannot answeropen
upload.timeout_msgate budget per request, capped at 600002000
upload.max_sizelargest upload the gate inspectsper install
heuristics.actionreport, quarantine or offreport
heuristics.thresholdscore above which a heuristic hit countsper install
signature.actionaction on a signature hitquarantine
hash.actionaction on a hash hitquarantine
notify.targetswho is told, and howper install
scan.windowwhen scheduled scans may runper install
scan.excludepath globs excluded from scheduled scansempty
realtime.enabledwhether the real-time watcher acts for this scopeon
quarantine.retention_dayshow long quarantined content is kept30
findings.retention_dayshow long findings are kept180

Reading and writing from the shell:

shelltrap policy get global
shelltrap policy set global signature.action=report hash.action=report
shelltrap policy set domain 42 heuristics.action=quarantine
shelltrap policy effective site 42

policy effective is the one to use in an argument: every key comes back with its value and its origin — domain, account, global or default — so inheritance is visible rather than inferred.

Ignore rules are not policy

Ignore rules live in their own API and are an administrator action. Four kinds: path_glob, signature, hash and user.

shelltrap ignore add --scope domain --scope-id 42 --kind path_glob --value 'cache/**' \
  --note 'generated cache'
shelltrap ignore list
shelltrap ignore remove <id>

An ignore rule suppresses the action, never the scan or the record of the finding. That distinction is what makes an ignore list auditable instead of a blindfold.

The local API

The API is HTTP/1.1 over the Unix socket /run/shelltrap/api.sock, mode 0600, owned by root. It is not reachable over TCP and it uses no cookies. The server reads SO_PEERCRED on every accepted connection and, in production, accepts only peer UID 0; anything else gets HTTP 403 before a backend call happens.

  • JSON media type application/json, request bodies capped at 1 MiB, page size capped at 500.
  • X-Shelltrap-Actor: <panel-user> makes the broker resolve the name and role itself. A role claimed by the client is never accepted, and the backend additionally restricts results to the actor’s own sites.
  • Every response carries X-Request-ID. On writes, a client-supplied ID also acts as replay protection: reusing it inside the memory window returns HTTP 409 replayed_request.
  • Rate limiting is per peer UID, 600 requests per minute by default; exceeding it returns 429.
  • Metrics come back in Prometheus text format, not JSON.
curl --silent --unix-socket /run/shelltrap/api.sock http://localhost/v1/health
curl --silent --unix-socket /run/shelltrap/api.sock \
  -H 'X-Shelltrap-Actor: reseller1' \
  'http://localhost/v1/findings?verdict=malicious&page=1&page_size=50'

Free-form path scanning (POST /v1/scan with a path) is available only to the system actor on a connection with peer UID 0. A panel or UI actor can never trigger root file access by supplying a path.

The CLI

shelltrap status
shelltrap health
shelltrap scan SITE | --account ACCOUNT | --path PATH
shelltrap findings list|show|resolve
shelltrap quarantine list|restore|purge
shelltrap policy get|set|effective
shelltrap ignore add|list|remove
shelltrap feeds list|update|rollback
shelltrap audit tail
shelltrap jobs list
shelltrap metrics

Global options are --json, --socket PATH (default /run/shelltrap/api.sock) and --actor NAME, and they come before the command. --json prints the API response unchanged; without it you get a deterministic human-readable table. Exit codes: 0 success, 1 API or response error, 2 usage error, 3 API unreachable.

root@web1 — /root
$ shelltrap license activate STL-7QK3M-P2VDA-XN94T-B6HRE-Z8SWF
plan             server
key prefix       STL-7QK3M
valid until      2027-09-04
token until      2026-10-04
fingerprint      e3b0c44298fc1c14…
servers          1 of 1
token written to /etc/shelltrap/license.token (0600, root)
$ shelltrap --json health
{"status":"ok","watcher_tier":"A","scanner_workers":2,"clamd":"ok",
 "ruleset_gen":"20260903T120000Z-0001","policy_gen":17,"queue_age_s":0,
 "overflows":0,"feeds":"ok"}

Activation and a health check with –json, which returns the API response unchanged.

Health and what to alert on

GET /v1/health returns the operating state:

{
  "status": "ok",
  "watcher_tier": "A",
  "scanner_workers": 2,
  "clamd": "ok",
  "ruleset_gen": "20260903T120000Z-0001",
  "policy_gen": 17,
  "queue_age_s": 0,
  "overflows": 0,
  "feeds": "ok"
}

status is ok or degraded. On degraded, degraded_reasons names every cause — live checks such as ruleset_missing, worker_dead, worker_landlock_missing, clamd_unavailable, namespace_fallback, policy_generation_unavailable, notify_target_unavailable and policy_error, plus internal error counters since process start.

A not_configured sub-status on an optional notify target does not by itself make the whole status degraded. An impaired scanner or a clamd outage must never be read as a clean scan.

Worth an alert rule, from our own observation checklist:

SignalSourceWhat normal looks like
watcher events and overflowsshelltrap_watcher_events_total, shelltrap_watcher_overflows_totaloverflows near zero; bursts visible at deploy time
queue ageshelltrap_queue_age_secondsunder 60 s in steady state
scans by verdictshelltrap_scans_total{verdict}clean dominates; check the reason spread on unscanned
worker restartsshelltrap_worker_restarts_totalzero
worker memoryps, systemctl statusstable RSS over days, no growth
clamd state and feed ageshelltrap healthstable
audit chainshelltrap audit tailunbroken, verification without error

Stop and investigate if overflows persist, queue age exceeds ten minutes, workers restart in a loop, worker RSS grows without bound, or customers complain about I/O load.

Retention and data protection

Defaults: findings 180 days, quarantine 30 days with advance warning, audit one year. All configurable. A purge is never silent. By default nothing leaves the host: outbound targets are allowlisted, and sample and telemetry code is not loaded without an opt-in. Logs contain no secrets and no file contents.

Next

Abridged from the Shelltrap architecture and API manuals, revision 2026-09-04.