From 3eeaa6d9a33f9294ade64c8ff26144fba86a379e Mon Sep 17 00:00:00 2001 From: grm Date: Fri, 18 Sep 2026 13:47:49 +0300 Subject: Security: Add HTTPS and TRUST_PROXY settings MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The app only ever speaks plain HTTP, so it could not know the site was served over TLS: generated links were http:// (the /webadmin bounce from a blog host sent the login page over http), the session cookie was never Secure and nothing sent HSTS. HTTPS=true fixes all three; ClearSessionCookie now uses the same attributes as the set, since a browser only replaces a cookie whose Secure flag matches. TRUST_PROXY=true makes the client address the last X-Forwarded-For entry — the one our proxy appended — so throttling and the log see real addresses instead of the proxy's; earlier entries are whatever the client sent and are ignored. Production startup warns when HTTPS is off. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/ratelimit.go | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) (limited to 'internal/web/ratelimit.go') diff --git a/internal/web/ratelimit.go b/internal/web/ratelimit.go index b019a4f..8f5fc94 100644 --- a/internal/web/ratelimit.go +++ b/internal/web/ratelimit.go @@ -3,6 +3,7 @@ package web import ( "net" "net/http" + "strings" "sync" "time" ) @@ -66,8 +67,21 @@ func (l *limiter) fill(b *bucket, now time.Time) float64 { return b.tokens } -// clientIP is the address requests are throttled by: the peer's. -func clientIP(r *http.Request) string { +// clientIP is the address requests are throttled by: the peer's, or with +// TRUST_PROXY the last X-Forwarded-For entry — the one our proxy appended +// (the README's nginx sets the header to $remote_addr alone); anything +// before it is whatever the client sent and could be forged. +func (s *Server) clientIP(r *http.Request) string { + if s.cfg.TrustProxy { + if xff := r.Header.Get("X-Forwarded-For"); xff != "" { + if i := strings.LastIndex(xff, ","); i >= 0 { + xff = xff[i+1:] + } + if ip := strings.TrimSpace(xff); ip != "" { + return ip + } + } + } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { return r.RemoteAddr @@ -81,7 +95,7 @@ func (s *Server) throttle(w http.ResponseWriter, r *http.Request, l *limiter, ke if l.allow(key) { return true } - logf("throttled %s %s from %s", r.Method, r.URL.Path, clientIP(r)) + logf("throttled %s %s from %s", r.Method, r.URL.Path, s.clientIP(r)) s.fail(w, r, http.StatusTooManyRequests, s.tr(r, "Too many requests. Try again in a minute.")) return false } -- cgit v1.2.3