# Part A — what client address an app sees (2026-10-01 ~09:00 UTC)

## demo-hp 9201, through its REAL tunnel (read only: two GETs of a 404 path, then the logs)
From DooPlex, public IPv4 37.191.56.193 (no IPv6 here, so only ONE outside address was available):
  curl https://wiki.enkisfelhom.hu/probe-1790844959-a            (once plain, once with X-Forwarded-For: 6.6.6.6)
cloudflared remote config (its own log): ingress *.enkisfelhom.hu -> https://traefik (noTLSVerify)
traefik access log (ClientHost = the peer it believes):
  172.18.0.5 - - [01/Oct/2026:08:56:09 +0000] "GET /probe-1790844959-a HTTP/1.1" 404 ... "bookstack@docker" "http://172.18.0.7:80"
  172.18.0.5 - - [01/Oct/2026:08:56:09 +0000] "GET /probe-1790844959-a HTTP/1.1" 404 ... "bookstack@docker" "http://172.18.0.7:80"
  (172.18.0.5 = the cloudflared CONTAINER, 172.18.0.3 = traefik, docker network traefik-public 172.18.0.0/16)
BookStack's own nginx log ($remote_addr):
  172.18.0.3 - - [01/Oct/2026:10:56:09 +0200] "GET /probe-1790844959-a HTTP/1.1" 404 ... "curl/8.14.1"
  172.18.0.3 - - [01/Oct/2026:10:56:10 +0200] "GET /probe-1790844959-a HTTP/1.1" 404 ... "curl/8.14.1"

## 9202 (scratch, no tunnel): an echo container (traefik/whoami v1.11) behind traefik with the catalog's usual labels (tools/echo.sh)
traefik 172.18.0.6, echo 172.18.0.5
T — the tunnel's hop SIMULATED: a container on traefik-public (where cloudflared sits) sends what cloudflared sends
    (X-Forwarded-For: 6.6.6.6, 203.0.113.9 · CF-Connecting-IP: 203.0.113.9):
  RemoteAddr: 172.18.0.6:42874      (traefik)
  X-Forwarded-For: 172.18.0.7       (the sending container — the forwarded chain was DROPPED by traefik)
  X-Real-Ip: 172.18.0.7
  Cf-Connecting-Ip: 203.0.113.9     (passed through untouched — traefik does not know this header)
L — from the LAN (DooPlex 192.168.0.180) straight to 9202:443, forging X-Forwarded-For 6.6.6.6, CF-Connecting-IP 7.7.7.7,
    X-Real-IP 8.8.4.4:
  RemoteAddr: 172.18.0.6:42874
  X-Forwarded-For: 192.168.0.180    (forgery dropped; the real LAN address — docker's DNAT keeps the source)
  X-Real-Ip: 192.168.0.180
  Cf-Connecting-Ip: 7.7.7.7         (THE FORGERY ARRIVES — any device that reaches :443 directly can set it)
Echo container and both test images removed afterwards.

## The answer
| path   | the app's TCP peer | X-Forwarded-For / X-Real-Ip | the real client is in        | forgeable by the client? |
|--------|--------------------|-----------------------------|------------------------------|--------------------------|
| tunnel | traefik            | cloudflared's container IP — THE SAME FOR EVERY VISITOR | CF-Connecting-IP only (set by Cloudflare's edge) | XFF: no (traefik drops it). CF-Connecting-IP: not through the tunnel (the edge overwrites it), YES from the LAN |
| LAN    | traefik            | the real LAN address        | X-Forwarded-For / X-Real-Ip  | no (traefik drops a forged chain) |

## Why no box-wide fix (Part A4 stops here)
traefik `forwardedHeaders.trustedIPs: [cloudflared]` would keep the tunnel's chain. Cloudflare APPENDS to a client's own
X-Forwarded-For (Cloudflare docs, not measured here), so an app would then receive "<anything the client wrote>, <real
client>, <cloudflared>". Every app that reads the LEFTMOST address (a common default) would then believe a client-written
value — a stranger could dodge or aim any per-address guard. Today that header is useless but honest. Trusting
CF-Connecting-IP instead is forgeable from the LAN (L above). cloudflared's address is also not fixed (172.18.0.5 here,
docker-assigned). A safe version needs a fixed-address network for cloudflared plus a rewrite to ONE address — not
available in traefik without a plugin (a new external dependency). So: per app, trusting no header.
