A rule set is not a stream of edits. It is a generation: an immutable, signed directory that is either fully present and verified, or not used at all. Vendor generation IDs look like 20260903T120000Z-0001; local overlays use local- plus 64 hex characters.
What is inside one
<gen>/
├── manifest.json
├── manifest.sig
├── yara/ *.yar, rules.yarc, rules.yarc.meta
├── hashes/ *.hsb + *.hsb.meta.json
├── allow/ *.sha256
└── clam/ *.ndb, *.hdb, *.fp, *.sfp, *.crtdb
manifest.json is canonical JSON with the generation ID, creation and expiry, engine requirements, the file list, counts and a key ID. Every file carries a SHA-256 digest, a size and full provenance: source URI, SPDX licence, author, upstream commit and whether it was modified. manifest.sig holds the Ed25519 signature.
How an update is verified
The order matters. The client checks the manifest digest from the signed index before it checks the manifest signature, then every file by size and digest, then verifies the whole generation again from the temporary directory before publishing it atomically into staging. HTTPS with normal certificate validation and same-origin is required for the base URL, redirects and artefacts.
A normal fetch accepts only a generation strictly newer than the active vendor base. Older generations are reachable only through an explicitly requested rollback, which is itself fully verified.
The corpus gate
Nothing is activated that has not passed a gate against two corpora on your own host. A known-good and a known-bad corpus must both exist. Every malicious hit in the good corpus is a hard gate failure. The detection ratio on the bad corpus is recorded as a metric; below 90 % it produces a warning rather than an automatic rejection, because a low ratio is a quality problem and a false positive is an outage.
Engine requirements are enforced against the versions the worker actually reports. Missing, malformed or too-low versions are refused fail-closed.
How often it runs
A systemd timer fetches on the configured interval, one hour by default, with ±10 % jitter. You can also fetch on demand:
shelltrap feeds list
shelltrap feeds update
Each fetch sends your signed licence token as a bearer credential. Without a valid token the feed service answers 401 and health reports feeds: unlicensed.
Retention
Retention always protects the current generation, explicitly preserved generations, and any vendor base a local overlay refers to. Beyond that, the newest keep_generations survive — three by default.
Why every rule carries an author
Only rules with an accepted licence and complete, matching provenance are shipped, and where an upstream licence requires the author, rule URI and licence notice to survive into detection reports, they do: the attribution travels into the panel display and into the notification mail. That is a licence obligation for some third-party rule sets, and it is also simply useful when you are deciding how much to trust a hit.