Security re-audit + adversarial red-team of the honeypot deception and
brute-force ban layer. Fixes (all with regression tests):
HoneypotController::trap
- BAN EVASION: keyed ban off getRealMethod() not isMethod('post') — the
latter honors _method / X-HTTP-Method-Override, so a POST spoofed as PUT
dodged the ban entirely. Now any non-safe verb to a decoy bans.
- REFLECTED-BAN vs EVASION: removed the Origin-based skip; the Origin header
is attacker-controlled, so it was a trivial universal ban-evasion (send a
foreign Origin) worse than the narrow griefing it prevented.
- UNMASK/500: mb_scrub all attacker-controlled meta (ua/query/path/creds) so
invalid UTF-8 can't throw JsonEncodingException on the array-cast column.
- Ban now runs FIRST and independently of the audit (separate guarded
try/catch) so a failing audit can never skip the ban; deceive() always 200.
- AUDIT DoS: per-IP hourly cap via atomic Cache::add(0)+increment.
DetectHoneytoken::trip
- Same guarded ban-first / audit-second structure + per-IP audit cap
(bounds an exempt or rotating source) + mb_scrub on the path.
BruteforceGuard::banNow
- Dedup the auth.ip_banned audit per IP+reason on a 60s window (was
full bantime): collapses the scanner flood without masking a legitimate
re-ban after an operator unban, and still audits distinct reasons.
Supply-chain / release / dashboard
- Pin prod base images (caddy/mariadb/redis) by @sha256 digest.
- Atomic branch+tag push in clusev-release.sh (no orphan untagged commit).
- Route github.ref_name through a validated $TAG env in ci-staging.yml.
- Strip inline credentials from the origin URL in set-repository-url.sh
(greedy match handles an @ inside the password).
- WireGuard peer name via Js::from in wire:click; settings badge shows the
real role; Threats pill/top_ip account for honeypot_login attempts.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|---|---|---|
| .. | ||
| README.md | ||
| clusev-release-request.path | ||
| clusev-release-request.service | ||
| clusev-release.sh | ||
| serve-request.test.sh | ||
README.md
Release bridge (host watcher) — DEV ONLY
The dashboard "Release" page (Deploy to Staging) cuts a release candidate by asking the host to do the git work, so the container never holds git credentials — the same isolation as the WireGuard bridge.
clusev-release.sh serve-request— readsrun/release-request.json(the dashboard wrote it), re-validates, bumpsconfig/clusev.php, commits, tagsvX.Y.Z-rcN, pushes to Gitea (token read host-side from/home/nexxo/.env.gitea), and writesrun/release-result-<id>.jsonfor the dashboard to poll. Roll back leaves no half-release if the push fails.clusev-release-request.path/.service— a systemd.pathwatcher that runs the handler when a request file appears.
Enable on the dev box
These units are installed only when CLUSEV_RELEASE_CONTROLS=true is in the project .env
(install.sh gates them). To enable manually:
cd /home/nexxo/clusev
sudo install -m 0644 docker/release/clusev-release-request.service /etc/systemd/system/
sudo install -m 0644 docker/release/clusev-release-request.path /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now clusev-release-request.path
The dev docker-compose.yml must bind-mount ./run to the container's
storage/app/restart-signal (shared with the WireGuard/restart bridges) so the dashboard's request
lands where the watcher looks.
Verify (manual gate)
This is the end-to-end gate (like the WireGuard runbook): with the watcher enabled and the flag on,
open /release, click a bump button, confirm, and check that vX.Y.Z-rcN lands on Gitea (and, via
the push-mirror, on GitHub-private). The handler never logs the Gitea token.