aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/ratelimit.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-18 13:47:49 +0300
committergrm <grm@eyesin.space>2026-09-18 13:47:49 +0300
commit3eeaa6d9a33f9294ade64c8ff26144fba86a379e (patch)
tree1c4bf44884ea59f7e3d9ca12008846f08bf72d16 /internal/web/ratelimit.go
parent254733b0566830a46e5a44c2d3127f57bfaceb65 (diff)
downloadblogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.tar.gz
blogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.tar.bz2
blogspace-3eeaa6d9a33f9294ade64c8ff26144fba86a379e.zip
Security: Add HTTPS and TRUST_PROXY settings
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/ratelimit.go')
-rw-r--r--internal/web/ratelimit.go20
1 files changed, 17 insertions, 3 deletions
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
}