From 10c3f8e6674cea2ddda72be56a2cb5cffb24b36f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Sun, 17 May 2026 15:30:34 +0200 Subject: [PATCH] docs(env): document TRUST_CF_CONNECTING_IP security implications MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The existing .env.example block warned that an attacker could forge the geo lookup if the flag was enabled on a non-Cloudflare deployment, but did not name the two additional downstream consumers of `getClientIp`: the IP-rate-limit bucket key for login/chat/coach and the audit_logs.ipAddress column for every privileged write. An operator who deploys behind nginx / Caddy / direct and copy-pastes the flag from a forum thread would let any caller defeat the rate-limiter AND forge the IP recorded against their audit trail. Rewrite the block to list every consumer explicitly and re-emphasise that the flag is fail-closed (default off, strict "=== '1'" gate, no server-side topology detection — the operator owns the decision). --- .env.example | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/.env.example b/.env.example index e414e5617..c83a6b3df 100644 --- a/.env.example +++ b/.env.example @@ -118,10 +118,27 @@ API_TOKEN_HMAC_KEY="REPLACE-WITH-OUTPUT-OF-openssl-rand-hex-32" # `X-Forwarded-For` chain stops at the Coolify / Caddy loopback and the # admin sign-in geo lookup has no signal to resolve from. # -# Set this to "1" ONLY when every public request goes through Cloudflare. -# A deployment without Cloudflare in front MUST leave this off — an -# attacker can set the header directly on the public internet and the geo -# resolver would happily report a forged location. +# Set this to "1" ONLY when every public request to HealthLog terminates +# at Cloudflare (Tunnel, Workers, classic proxy) before it reaches your +# origin. Deployments behind nginx, Caddy, Traefik, an AWS ALB, or any +# direct-to-Next.js exposure MUST leave this off. +# +# Security implications when the flag is mis-set: +# - `cf-connecting-ip` becomes the IP-rate-limit bucket key for +# login / chat / coach. An attacker who can set the header rotates +# it per request and defeats every IP-keyed throttle. +# - The same value lands in the `audit_logs.ipAddress` column for +# every `measurement.create`, `medication.intake`, sign-in, and +# admin action. A misconfigured flag lets any caller forge the +# IP recorded against their actions across the audit trail. +# - The admin sign-in overview's "Standort" column reads the same +# value via geo-backfill; a malicious caller can poison the column +# with a chosen IP per request. +# +# Code-level validation is strict (only "=== '1'" enables, value is +# regex- and length-bounded), but the gating decision is up to the +# operator — there is no way for the server to detect whether the flag +# matches the real deployment topology. When in doubt, leave it off. # TRUST_CF_CONNECTING_IP=""