feat(backend): trust reverse-proxy X-Forwarded-For for the client IP

The auth rate limiter keys on c.ClientIP(), but the router was built with
gin.New() and never called SetTrustedProxies — so Gin trusted all proxies by
default. Behind a host reverse proxy that meant the limiter either bucketed
every request under the proxy's IP, or (with the port reachable directly) could
be bypassed by a forged X-Forwarded-For.

NewRouter now takes a trusted-proxy list and configures SetTrustedProxies,
returning an error on an invalid list so misconfiguration fails fast at startup.
The list comes from a new TRUSTED_PROXIES config (CSV of CIDRs/IPs), defaulting
to loopback plus the Docker bridge ranges a host proxy reaches the container
through.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-15 14:51:44 +03:00
parent 88e07f0723
commit 99668ec0d8
4 changed files with 44 additions and 4 deletions
+6 -1
View File
@@ -114,12 +114,17 @@ func main() {
aclHandler := handler.NewACLHandler(aclSvc)
auditHandler := handler.NewAuditHandler(auditSvc)
r := handler.NewRouter(
r, err := handler.NewRouter(
authMiddleware, authHandler,
fileHandler, tagHandler, categoryHandler, poolHandler,
userHandler, aclHandler, auditHandler,
cfg.StaticDir,
cfg.TrustedProxies,
)
if err != nil {
slog.Error("building router", "err", err)
os.Exit(1)
}
// ReadHeaderTimeout bounds slow-header (Slowloris) attacks; body read/write
// are left unbounded so large file uploads and downloads can stream.