Mundial de Clicks is designed to run behind Cloudflare in production. The security model relies on two secrets —Documentation Index
Fetch the complete documentation index at: https://mintlify.com/midudev/mundial-de-clicks/llms.txt
Use this file to discover all available pages before exploring further.
ORIGIN_GUARD_SECRET and VOTER_ID_SECRET — combined with Cloudflare’s trusted IP header to protect the origin against direct access, IP spoofing, and daily-limit resets. This page explains each protection layer and how to configure them together.
Cloudflare IP Extraction
When a request arrives through Cloudflare, the edge injects the real client IP in theCF-Connecting-IP header. Mundial de Clicks reads this header in getCloudflareClientIp() and uses it as the authoritative client identity for rate limiting and daily vote caps. In production the app only trusts CF-Connecting-IP — it does not fall back to X-Forwarded-For, which can be spoofed by clients. In local development (non-production NODE_ENV) it additionally accepts X-Real-IP from a local reverse proxy.
ORIGIN_GUARD_SECRET
The origin guard is a shared secret header check that prevents attackers from bypassing Cloudflare by hitting the origin server directly. WhenORIGIN_GUARD_SECRET is set, the Astro middleware rejects every non-health request that does not carry the correct x-origin-guard header value. Because Cloudflare is the only system that knows this secret (via a Transform Rule), direct requests to the origin receive a 403 Forbidden response.
How it works in the middleware (src/middleware.ts):
/api/health path is explicitly excluded so that Coolify’s health checks continue to work even when the guard is active.
Step 1 — Set the secret in Coolify:
- Expression:
http.request.uri.path ne "/api/health" - Action: Set static header
- Header name:
x-origin-guard - Value:
your-long-random-secret(same value as the env var)
- Header name:
VOTER_ID_SECRET
Thevoter_id cookie is a persistent browser identity used as a secondary daily vote cap alongside the IP-based cap. It is an HMAC-SHA256-signed UUID that survives browser sessions (180-day Max-Age) and is tied to a unique voter identity rather than a network address. This makes it harder to reset daily limits by simply reconnecting or switching networks.
Cookie format: {uuid}.{base64url-hmac-sha256-signature}
The signing and verification logic from src/lib/voter-id.ts:
timingSafeEqual to prevent timing attacks during signature comparison.
Secret resolution order:
VOTER_ID_SECRET(if set)ORIGIN_GUARD_SECRET(fallback, ifVOTER_ID_SECRETis not set)'dev-voter-id-secret'(development only, whenNODE_ENV !== 'production')
IP Sanitization
All IP values extracted from request headers are sanitized before being used as DragonFly key components (e.g.rl:{ip}:{window}). The sanitization:
- Truncates the value to 45 characters (the maximum length of an IPv6 address)
- Accepts only valid IPv4 (
\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3}) or IPv6 ([0-9a-fA-F:]+) patterns - Falls back to the configured fallback IP (default
0.0.0.0) for any value that does not match
Production Checklist
Before exposing a Mundial de Clicks deployment to public traffic, verify every item in this checklist:Restrict origin to Cloudflare IPs
Configure your server firewall, Coolify network policy, or upstream proxy to only accept inbound HTTP/S connections from Cloudflare’s published IP ranges. This is the foundation that makes all header-based protections meaningful.
Set ORIGIN_GUARD_SECRET and configure Cloudflare Transform Rule
Set
ORIGIN_GUARD_SECRET in the Coolify environment, then create a Cloudflare Transform Rule to inject x-origin-guard: <secret> on all requests except /api/health. Test by hitting the origin IP directly — you should receive a 403.Set VOTER_ID_SECRET to a stable value
Generate a secret with
openssl rand -base64 32, set it as VOTER_ID_SECRET in Coolify, and treat it as permanent. Document it in your secrets manager so it is not accidentally rotated during a redeploy.Configure CAP_API_URL (captcha required in production)
Set
CAP_API_URL to point to your Cap standalone server. Without it, hasCaptcha is false and POST /api/vote returns 503 in production. Both DragonFly (REDIS_URL) and CAP_API_URL must be set for the vote API to accept requests.