feat(security): mirror anti-DDoS blocks to Cloudflare edge via API
Gitea Actions Runner Test / test-job (push) Successful in 1s
CI / check (push) Successful in 30s
CI / tests-integration (push) Successful in 1m38s
CI / tests-unit (push) Failing after 1m40s
CI / tests-ui (push) Successful in 2m28s
CI / preflight (push) Skipped
CI / deploy (push) Skipped

- gate creates a zone IP Access Rule (block) for proxied offenders that hit the block threshold, deduped until the tiered block expires
- cloudflare-api lib: verified endpoints, create/delete/verify/list helpers, Redis-backed tracking + 30s TTL sweep (instrumentation worker + admin render)
- runtime toggle cloudflareAutoBlock in antiddos config; boot default CLOUDFLARE_AUTO_BLOCK_ENABLED
- admin panel: Cloudflare edge-blocks card with verify + remove-rule actions; unban also lifts the edge block
- credentials live in env only (CLOUDFLARE_API_TOKEN / CLOUDFLARE_ZONE_ID)
This commit is contained in:
openhands committed 2026-09-22 23:27:15 +02:00
1 parent f0c27eb815
commit 4479753160
9 files changed
+1157 -1

No files matched your search

+13
View File
@@ -5,6 +5,8 @@ import { NextResponse } from "next/server";
import { env } from "@/env";
import { getAntiddosConfig } from "@/lib/antiddos-config";
import { resolveClientIp } from "@/lib/client-ip";
import { isCloudflareProxied } from "@/lib/cloudflare";
import { maybeAutoBlockCloudflare } from "@/lib/cloudflare-api";
import { classifyDdos, isSuspiciousPath } from "@/lib/ddos";
import { rateLimit } from "@/lib/rate-limit";
import { redis } from "@/lib/redis";
@@ -110,6 +112,17 @@ export async function enforceDdosRateLimit(
const ttl = blockTtlForViolations(violations, config.blockTiers);
if (violations >= config.maxViolations) {
await redis.set(blockKey, "1", "EX", ttl);
// Mirror the host-level block to the Cloudflare edge (IP Access
// Rules) so a repeat offender is shed before it reaches the
// origin. Only when this request demonstrably transited
// Cloudflare — that is when the client IP is trustworthy.
void maybeAutoBlockCloudflare({
ip,
ttlSeconds: ttl,
category,
enabled:
config.cloudflareAutoBlock && isCloudflareProxied(req.headers),
});
}
return { outcome: "block", retryAfterSeconds: ttl };
} catch {