feat(security): Cloudflare-aware IP trust and admin-tunable anti-DDoS

- resolveClientIp: trust cf-connecting-ip only behind cf-ray/cdn-loop, use nginx x-real-ip otherwise (anti-spoof)
- antiddos-config: Redis-backed live config (antiddos:config) with 30s cache, 13 ANTI_DDOS_* env vars
- ddos-guard: consume tunable rates/tiers via getAntiddosConfig
- admin panel at /admin/devops/antiddos (save/reset/unban actions, PERMS.SETTINGS_VIEW)
- register new admin page in housekeeping migration matrix (146 -> 147)
This commit is contained in:
openhands committed 2026-09-22 22:22:51 +02:00
1 parent fd4d0fa1cb
commit f0c27eb815
17 files changed
+1151 -63

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
/**
* Cloudflare presence detection.
*
* Only headers that a Cloudflare edge adds to every transit are treated as
* proof the request passed through Cloudflare: `CF-Ray` is stamped by the
* edge and `CDN-Loop: cloudflare` is prepended on CDN transits. A bare
* `CF-Connecting-IP` is *not* sufficient — a direct client to the origin can
* send that header itself — so it is only trusted in combination with one of
* the edge-stamped fingerprints.
*/
export function isCloudflareProxied(headers: Pick<Headers, "get">): boolean {
const ray = headers.get("cf-ray")?.trim();
if (ray) return true;
const loop = headers.get("cdn-loop")?.trim();
if (loop) return true;
return false;
}
/**
* Which client-IP header the stack should trust for a given request. When the
* request demonstrably transited Cloudflare, `CF-Connecting-IP` carries the
* real client; otherwise only the ingress-set `X-Real-IP` / `X-Forwarded-For`
* (derived by nginx from the actual TCP peer) may be trusted.
*/
export function preferredClientIpHeader(
headers: Pick<Headers, "get">,
): "cf-connecting-ip" | "x-forwarded-for" | "x-real-ip" | "none" {
if (isCloudflareProxied(headers)) return "cf-connecting-ip";
if (headers.get("x-real-ip")?.trim()) return "x-real-ip";
if (headers.get("x-forwarded-for")?.trim()) return "x-forwarded-for";
return "none";
}