Documentation
Licensing and activation
One licence per server, verified offline against a signed token. The licence service does not have to be reachable for the scanner to work — but it does have to be reachable to renew.
The pieces
| Term | Meaning |
|---|---|
| Licence key | STL-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX — Crockford Base32, 25 payload characters including two check characters. Generated when the licence is created and returned in clear text exactly once; the service stores only sha256(key) plus the first nine characters as a display prefix. |
| Licence | The record: plan, maximum servers (1 by default), status (active, suspended, terminated), expiry, and the link to your account. |
| Activation | Binding a licence to a server fingerprint. At most max_servers at a time; deactivate frees a slot. |
| Licence token | An Ed25519-signed document the daemon verifies offline. |
| Server fingerprint | sha256 over a fixed prefix and the contents of /etc/machine-id. The hostname is sent for information only. |
Activation
shelltrap license activate STL-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
shelltrap license status
shelltrap license renew
shelltrap license deactivate
The token lands in /etc/shelltrap/license.token, root, mode 0600. The key itself is stored
only as a prefix in /etc/shelltrap/license.key, also 0600, so renewals do not need you to
type it again. If you would rather not have the key on the server at all, delete that file and
renew manually.
A shelltrap-license.timer runs the renewal daily with up to an hour of jitter. That service is
a separate systemd unit from the daemon, because it is the only component that needs AF_INET
or AF_INET6 at all — shelltrapd itself is restricted to AF_UNIX.
$ 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, then a health check. Fields shown are the ones the CLI documents: plan, prefix, validity, token validity, fingerprint and server quota.
What is inside the token
The payload is canonical JSON with sorted keys. The fields that matter operationally:
not_after— token validity, 30 days after issue. After that the token is invalid even if the licence itself runs longer. This is what forces renewal.license_expires_at— paid until, plus 14 days of grace. If it falls beforenot_after, the earlier date wins.fingerprint— the server this token is for. A token moved to another machine does not work.features— the capability list the licence unlocks.key_id— which signing key to verify against.
Verification uses pinned licence public keys. The package ships the Panomity licence public
key at /usr/share/shelltrap/keys/license-panomity.pub, and configuration may only add keys,
never replace that pin. That is deliberate: it stops a server operator from swapping the trust
anchor.
The three states
| State | When | What the daemon does |
|---|---|---|
licensed | valid token, matching fingerprint | normal operation |
grace | token expires in under 7 days and renewal is failing | normal operation, plus a warning in the log, health degraded with license_renewal_failed, and one admin notification a day |
unlicensed | no token, bad signature, expiry passed, or a foreign fingerprint | see below |
In unlicensed the daemon does not crash and does not restart-loop:
- the watcher keeps running and events are counted but not scanned,
- the scheduler plans nothing,
- the upload gate answers
allowwith reasonunlicensed, - health reports
status: "unlicensed"with a precisedegraded_reasonsentry —license_missing,license_expired,license_invalidorlicense_fingerprint_mismatch, - an audit entry
license.stateis written, - the metrics
shelltrap_license_state{state=…}andshelltrap_license_expires_secondsreflect it, - the feed service answers 401 and the feed controller reports
feeds: unlicensed.
The design goal there is worth stating: an expired licence must be loudly useless, never quietly dangerous. A gate that started denying uploads because an invoice was late would be a worse product than one that says so in health and lets traffic through.
Feeds and the licence
Every feed fetch sends the compact token as a bearer credential. Without a valid token the service answers 401. Signatures, updates and support are the commercial boundary of this product — see signature feeds for what arrives when the token is good.
Downloads
GET /v1/downloads/index.json is readable without a licence, so a website can display the
current versions. The packages themselves are served only against an active licence:
GET /v1/downloads/<file>?key=STL-…. The service logs the key prefix, never the key. There
are no unauthenticated package links, which is why the download page
has a form
instead of a list of URLs.
Moving a licence to another server
# on the old machine
shelltrap license deactivate
# on the new machine
shelltrap license activate STL-XXXXX-XXXXX-XXXXX-XXXXX-XXXXX
Re-activating a fingerprint that is already active simply issues a new token and does not consume a second slot, so a renewal never locks you out of your own server.
What a licence check cannot do
An attacker with root on your server can patch any binary on it, ours included, and no client-side check survives that. We say so in the licence terms as well. The commercial protection rests on signatures, updates and support being reachable only with an active licence, and on no source code being delivered.
Next
Abridged from the Shelltrap licensing manual, revision 2026-09-04.