Files
felhom.eu/documentation/audits/visitors-2026-10-01/A/sweep/sweep-2.md
T

6.4 KiB

Catalog sweep 2/4 — who reads the visitor's address (READ in source at each pinned tag; not measured live)

Subagent report, 2026-10-01 evening, copied verbatim in substance. Apps: gitea glance gokapi grafana gramps-web home-assistant homebox homepage immich jellyfin karakeep kimai komga mealie.

App (tag) Reads forwarded headers? How / setting (default) Used for Verdict for the new chain Evidence
gitea 1.27.3 Only from a trusted peer; default trust = loopback, so traefik is not trusted chi proxy: X-Real-IP first, then XFF count-from-right (REVERSE_PROXY_LIMIT=1); REVERSE_PROXY_TRUSTED_PROXIES (default 127.0.0.0/8,::1/128) logs ("Failed authentication attempt … from"), InitialIP; no IP lockout SAFE AS IS (sees traefik). Trusting 172.16.0.0/12 would show the LAN client, but cloudflared on the tunnel (X-Real-IP wins). Never forgeable modules/setting/security.go:133-137; routers/common/middleware.go:33,123-133; chi-middleware/proxy v1.1.1 middleware.go:50-73; routers/web/auth/auth.go:310
glance v0.8.5 Only with server.proxied: true (off; not in our seed) leftmost XFF when on login limit by IP, only with auth: (our seed has none) SAFE AS IS. Never set proxied: true (leftmost) internal/glance/glance.go:364-389; auth.go:24-25,142-160
gokapi v1.9.6 always: first parseable XFF, X-Real-IP, peer leftmost, no setting download log only when SaveIp (seed: false); no login limit SAFE AS IS (unused) internal/logging/Logging.go:43-48,65-93
grafana 13.2.3 always X-Real-IP first, then leftmost XFF; no trust setting lockout by USERNAME (5/5 min); IP lockout OFF by default; logs, session client-IP SAFE AS IS (X-Real-Ip is traefik-set: cloudflared on the tunnel, LAN client on the LAN). Do not turn on IP lockout pkg/web/context.go:71-96; conf/defaults.ini:498-507; loginattemptimpl/login_attempt.go:65-99
gramps-web v25.6.0 no (flask_limiter get_remote_address = TCP peer) peer 1/second on token/login/register, keyed on the peer = ONE bucket for all SAFE AS IS, no change from Part A gramps-web-api v3.3.0 ratelimiter.py:5-9; token.py:73,104,128,143 (API version INFERRED from FROM dmstraub/gramps-webapi:latest)
home-assistant 2026.9.4 yes; template sets use_x_forwarded_for: true, trusted_proxies: [172.16.0.0/12] walks from the RIGHT skipping trusted; all trusted → leftmost; non-IP entry → 400 ip_ban (threshold -1 = off); LAN privilege: local_only users, remember-me preselect SAFE AS IS and FIXED by Part A: today every tunnel visitor arrives as cloudflared's PRIVATE address → "local" → a local_only user can sign in from the internet. After: skips 172.16.253.2, takes the real public client components/http/forwarded.py:83-144; http/auth_util.py:15-43; util/network.py:51-53
homebox 0.26.2 only with HBOX_OPTIONS_TRUST_PROXY=true (default false, not set) X-Real-IP first, then leftmost XFF login/forgot/reset limiter keyed IP|path (5/min) SAFE AS IS (not forgeable; but one bucket = a stranger can lock everyone out — as today). Turning trust on helps the LAN only (X-Real-Ip = cloudflared on the tunnel); also makes it trust X-Forwarded-Host — optional, small gain backend/app/api/middleware.go:454-490,556-575; internal/sys/config/conf.go:74,179-184
homepage v1.13.2 no — no auth; Host check on /api/* SAFE AS IS src/middleware.js:3-18
immich v3.2.4 yes (Express trust proxy) walks from the RIGHT (proxy-addr); IMMICH_TRUSTED_PROXIES default linklocal,uniquelocal (+loopback) logs only; no IP lockout SAFE AS IS and sees the real client after Part A server/src/app.common.ts:49; config.repository.ts:327; auth.service.ts:71
jellyfin 10.11.11 only when KnownProxies set (default empty) ASP.NET ForwardedHeaders from the RIGHT LAN privileges (remote access per user, remote bitrate, public user list, restart for non-admins, ForgotPassword); lockout per user NEEDS A SETTING — and a RISK TODAY that Part A alone does not change: the TCP peer is traefik (private) → every internet visitor is "LAN". Fix: KnownProxies 172.16.0.0/12 in network.xml (no env var) ApiServiceCollectionExtensions.cs:169-190,282-326; NetworkManager.cs:309-340,942-960; UserManager.cs:595-596
karakeep 0.33.2 always (request-ip) X-Client-IP, then LEFTMOST XFF, then CF-Connecting-IP, … login + tRPC limits keyed by IP — only with RATE_LIMITING_ENABLED=true (default false, not set) SAFE AS IS (limiter off). Do not turn the limiter on — after Part A its key would be the client-written leftmost apps/web/server/auth.ts:129-137; packages/trpc/lib/rateLimit.ts:21-39; request-ip src/index.js:39-41,59-97
kimai 2.67.0 only from TRUSTED_PROXIES (image default nginx,localhost,127.0.0.1) Symfony: from the RIGHT, dropping trusted login throttling 5/5 min (Symfony default username+IP plus a per-IP limit — from Symfony docs, not Kimai code); IP-keyed limiters (session-ID guard, password reset, old API tokens) NEEDS A SETTING: today every key is traefik → one attacker trips the IP limiters for all. TRUSTED_PROXIES=127.0.0.1,172.16.0.0/12 → tunnel real client, LAN client Dockerfile:256; config/packages/security.yaml:70-72; rate_limiter.yaml
komga 1.28.0 always (forward-headers-strategy: framework) LEFTMOST the authentication-activity audit IP only SAFE AS IS for security; the audit IP becomes client-written on the tunnel (today: cloudflared). SERVER_FORWARDHEADERSSTRATEGY=native would fix it — not without a live test application.yml:63; LoginListener.kt:30-96
mealie v3.28.0 yes; and /api/auth/token reads raw XFF LEFTMOST log lines; lockout per ACCOUNT SAFE AS IS; the logged IP becomes client-written on the tunnel. No setting fixes it routes/auth/auth.py:141-147; credentials_provider.py:41-54

Notes from the report: leftmost readers (glance if proxied, gokapi, karakeep, komga, mealie) use the address for nothing or for logs/audit only as configured — two switches must stay off (karakeep RATE_LIMITING_ENABLED, glance proxied). Not checked live: ASP.NET (Jellyfin) and HA with a 3-entry XFF (HA refuses when X-Forwarded-Proto has neither 1 entry nor as many as XFF — traefik sends 1); Kimai's exact throttling keys. Side observations: glance's seed has no auth: (public dashboard); homepage /api/* refuses a Host not in HOMEPAGE_ALLOWED_HOSTS (inferred, not set by the template).