[5.2.0] — 2026-09-08
Opt-in CrowdSec (engine and a registered firewall bouncer) and a nightly integrity check. Neither is installed by setup.sh or cipi self-update. There is no WAF.
Added
cipi crowdsec enable. Installs the engine andcrowdsec-firewall-bouncer-nftables(or-iptables) registered withcscli bouncers add— decisions without a bouncer ban nothing. Acquisition reads/home/*/logs/nginx-{access,error}.log, which is where every Cipi vhost actually logs;/var/log/nginxalone holds only the catch-all server block, so the nginx scenarios would never see application traffic. On Ubuntu 24.04 iptables is nft, so the bouncer matches fail2ban'siptables-multiport. enable refuses below 512MB MemAvailable, refuses if nginx looks like a reverse proxy withoutset_real_ip_from+real_ip_header(would ban Cloudflare, not the attacker), and allowlists this SSH session.disableflushes CrowdSec chains/tables, then purges, so DROP rules cannot outlivecscli.- Rescue TLS listener. enable starts a TLS server on a high port. The TLS handshake runs in the connection's own thread with a deadline, never in
accept(): wrapping the listening socket lets one peer that opens a connection and sends nothing hold the break-glass path shut for as long as it likes. Concurrent connections are capped below the unit'sTasksMax. One GET with a one-shot token allowlists the TCP peer (neverX-Forwarded-For), unbans that IP in CrowdSec and fail2ban, mails a new token, and dies. Not a login, not an SSH key. UFW opens the port; an ACCEPT is punched at the head of the CrowdSec chain (and again on every bouncer restart) so a DROP-all ban cannot close it.cipi crowdsec rescue token|rotate.statusshows the port and cert fingerprint, not the token. - Allowlists. Localhost, RFC1918, Let's Encrypt HTTP-01. GitHub webhook CIDRs from
api.github.com/meta.hooks(fail-open: a dead fetch keeps the previous file). GitLab.com egress CIDRs are hardcoded, dated 2026-09-07, and documented as rotting. cipi ban list|unbanincludes CrowdSec when the engine is up.cipi scan. Nightly (04:40): integrity ofcurrent/(orhtdocs/) against a sha256 manifest written at every successful deploy and rollback, then one ClamAV process on upload dirs only with rfxn PHP-webshell signatures. Manifests live in/var/lib/cipi/manifestsas root:root 0600, never in the app home:open_basedirgives PHP all of/home/<app>/, so a manifest kept there is editable by the very webshell the check exists to catch. The webhook deploy runs as the app user and reaches the writer through one sudo entry pinned to its own app name, and every re-baseline is written toevents.log— a manifest rewrite with no deploy beside it is the thing to look for. Comparison slices sha256sum records by offset instead of splitting on whitespace: names with spaces were truncated in the report, and GNU coreutils escapes records holding a backslash or a newline, which a hash-anchored filter dropped entirely (a webshell calledsh\ell.phpwas invisible). Timeouts, failed signature updates and releases that could not be hashed in full mailscan_incomplete— not "clean". Extra/changed files mailscan_integrity. FPMopen_basedir/ pool-user drift is included in that mail. Isolation itself (per-app Unix user +open_basedir) was already Cipi; this only checks it is still there.enablerefuses below 2GB MemAvailable or 3GB free on/var(--forceto override): clamscan loads the whole signature set per run, and an OOM kill at 04:40 may take MariaDB with it.- Shell tab-completion, installed automatically.
setup.shand everycipi self-updatewrite/etc/bash_completion.d/cipi,/usr/share/zsh/vendor-completions/_cipi(when that directory exists) and an/etc/profile.d/cipi-completion.shloader that fires for every interactive bash and zsh shell — with or without thebash-completionpackage, whichsetup.shnow also installs. Nobody edits a dotfile; a new login shell has it.cipi completion bash|zshis there only for a non-login shell or a custom rc file (--printemits the raw script and changes nothing). It completes the top-level verbs, each verb's sub-commands and flags, andcipi help <topic>; app names are filled in from/etc/cipi/apps-public.jsonwhen the shell's user can read it (a root session, or thecipi-apigroup), and everything else is a static word list that works for any user.sudo cipi <TAB>resolves through the stock sudo completion. Hand-written, not framework-generated:tests/verify-5.2.0.shfails if the verb list drifts from the dispatch incipi. - Migration 5.2.0. Installs
cipi-scan-manifestand the two rescue helpers, creates the manifest store, grants each existing app its manifest sudo entry, and addsSSH_CLIENT/SSH_CONNECTIONtoenv_keepin/etc/sudoers.d/cipi-sudo. The helper install is not optional housekeeping:self-updatenever re-execs, so the copy loop running during the upgrade is the pre-5.2.0 one and knows nothing about the new binaries — without this,crowdsec enablerefuses to start the rescue listener and no deploy ever refreshes a manifest.crowdsec enableallowlists the session it is typed from, andsetup.shsetsPermitRootLogin no— so the normal path issudo cipi, and sudo'senv_resetwas dropping the only variables that carry the operator's IP. The candidate file is validated withvisudo -cbefore it replaces the live one. Nothing else is migrated: CrowdSec and the scan stay off.
Notes
- CrowdSec is 24/7 after enable. The scan is the nightly job.
cipi statuslists CrowdSec, its bouncer and the rescue listener only when those units exist. A rescue listener that has stopped is the one thing you want to find before you need it, not after.- The rescue listener runs as root (redeeming needs it) but with
NoNewPrivileges,ProtectHome=read-only,PrivateTmpand aMemoryHighcap.ProtectSystemandSystemCallFilterare deliberately left off — hardening that can break break-glass is worse than the surface it removes. - The rescue throttle applies to wrong tokens only. A correct token is always honoured, whatever the peer typed before it.
cipi ban listreads.decisions[]out ofcscli decisions list -o json, which returns alerts. Reading.valueat the top level printed a decision count and then no addresses at all.cipi ban unbanasks before it claims success, becausecscli decisions deleteexits 0 whether it removed something or nothing.- The integrity check tells you a tree changed between deploys. An attacker who owns the app user can still re-baseline through the sudo entry, so read
events.logalongside the alert. - Pre-symlink checkpoint and inotify on uploads are not this release.