fix(backend): honor X-Forwarded-For via configurable trusted proxies (#2701)

This commit is contained in:
Clément Fiere
2026-08-15 23:18:57 +02:00
committed by GitHub
parent b87cbcf3ed
commit 98db560d83
11 changed files with 944 additions and 12 deletions
+35
View File
@@ -155,6 +155,41 @@ Do not infer security from `auth.jwtSecret`: setting it currently does not turn
[Voice Mode plugin](https://github.com/kdlbs/kandev-plugin-voice), and its transcription key lives
in that plugin's own settings. Kandev ignores the old key and no longer serves `/api/v1/transcribe`.
### Trusted proxies for X-Forwarded-For
`KANDEV_TRUSTED_PROXIES` lists the reverse proxies whose forwarded client IP
Kandev trusts. Format: a comma-separated list of IP addresses or CIDR ranges,
for example `10.0.0.0/8,192.168.0.0/16`. IPv6 addresses and CIDRs are
accepted. When the TCP peer of a request is in the list, the client IP is
read from `X-Forwarded-For` (then `X-Real-IP`); otherwise those headers are
ignored and the TCP peer address is used. The resolved IP feeds the login
session record (Settings > Account > Security) and the login rate-limiter
key.
Default: unset, meaning no trusted proxies. Forwarded headers are ignored
entirely and the recorded client IP is always the TCP peer. This is the
secure default: gin would otherwise trust every proxy by default, which lets
a directly reachable backend accept a spoofed client IP.
Security implication: only list proxies you control that always overwrite the
forwarded headers. A backend reached directly by a caller whose address
falls inside a listed range can have `X-Forwarded-For` spoofed by that
caller, which also defeats the ClientIP-keyed login rate limiter (login
attempts are limited per IP+email). Callers outside the listed ranges still
fall back to their own peer address.
An entry that is neither a valid IP nor a valid CIDR is rejected at startup
with a warning naming the bad value, and the whole variable is ignored (fail
closed: no partial trust). A trailing or doubled comma is treated the same
way. The backend never crashes on a bad value.
The variable is read once at startup and must reach the backend process: set
it in the environment of the process that launches kandev, for example a
systemd drop-in for `kandev.service` (`systemctl --user edit
kandev.service`) or the container environment. The supervisor manifest at
`~/.kandev/supervisor/launch.json` is generated by the launcher on every
launch and is not an environment configuration source.
### Logging
| YAML key | Environment variable | Default | Current behavior |