Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0159rPz1ZhFKsS53msqPYxtS
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).