From fb6067b620f281f860c24430a96574cff90e3f99 Mon Sep 17 00:00:00 2001 From: Erich Blume Date: Mon, 11 May 2026 13:44:22 -0700 Subject: [PATCH] C1: shower-specific rate-limit zone for venue-wifi NAT MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Default `general` zone (10r/s burst=20) is tuned for internet drive-by traffic. At the party, 30 guests scanning the splash QR from one venue-wifi NAT'd public IP would each fetch HTML + ~5 static assets within a few seconds — easily clearing burst=20, and the second-wave guests would see 503 with no auto-retry. New shower_general zone (50r/s burst=200) absorbs that simultaneous- load spike. Exploit scanners still trip it: the 45.88.138.44 burst we already saw in Loki fired ~30 req in 2s, well above the new sustained 50r/s when extrapolated, and burst=200 is still a hard cap on instantaneous spikes. Self-healing: `limit_req` is a token bucket — no persistent ban, nothing to manually flush. A guest who trips it auto-recovers within ~1s; tuning here is about not tripping it on legit traffic in the first place. Co-Authored-By: Claude Opus 4.7 (1M context) --- fly/nginx.conf | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/fly/nginx.conf b/fly/nginx.conf index 089971c..570e6c9 100644 --- a/fly/nginx.conf +++ b/fly/nginx.conf @@ -34,6 +34,15 @@ http { # bucket. $http_fly_client_ip has the actual client IP. limit_req_zone $http_fly_client_ip zone=forge_auth:10m rate=3r/s; + # Shower-specific zone: loose enough that ~30 guests sharing a single + # venue-wifi NAT'd public IP can all scan the QR and load the splash + # (HTML + a handful of static asset hits each) without anyone tripping + # the limit. 50r/s + burst=200 covers the simultaneous-load spike; + # exploit scanners still trip it (e.g. the .env-sweeping bot we saw + # fired ~30 req in 2s — that pattern stays caught). See the + # shower.eblu.me server block for the matching `limit_req`. + limit_req_zone $http_fly_client_ip zone=shower_general:10m rate=50r/s; + # fail2ban deny list — banned IPs are written here by fail2ban and # checked via the $forge_banned variable. The file is touched at # container start to ensure it exists. @@ -318,8 +327,13 @@ http { listen 8080; server_name shower.eblu.me; - # General per-IP rate limit (cushion for the splash page + form posts) - limit_req zone=general burst=20 nodelay; + # Per-IP rate limit. shower_general (50r/s, burst=200) instead of + # the global `general` zone because at the party, guests on the + # venue's wifi all NAT through a single Fly-Client-IP — 30 guests + # scanning the QR at once would each fetch HTML + a few static + # assets, easily clearing 20 burst on `general`. Exploit scanners + # still trip it (sustained ≫ 50r/s patterns). + limit_req zone=shower_general burst=200 nodelay; # Image uploads from /host/'s prize cropper are ~150-300 KiB JPEGs. # The host page itself isn't reachable here, but /media/ reads can