diff --git a/README.md b/README.md index 02ec33e..cde7d66 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,9 @@ grep -n "r\.\(Get\|Post\|Put\|Delete\|Patch\)" backend/main.go - Authenticated users: 120/min - Admin search: 60/min - Admin-only routes: none (trusted) +- ⚠️ **Gap: Rate limiter doesn't read CF-Connecting-IP** - behind Cloudflare all users share one bucket +- ⚠️ **Gap: No HSTS header** - add when HTTPS working +- ⚠️ **Gap: No Referrer-Policy** - for analytics tracking **Input Validation:** - Backend validates all inputs against DB schema constraints diff --git a/backend/mw/ratelimit.go b/backend/mw/ratelimit.go index 41f5618..24a6cad 100644 --- a/backend/mw/ratelimit.go +++ b/backend/mw/ratelimit.go @@ -4,8 +4,6 @@ import ( "net/http" "sync" "time" - - "github.com/go-chi/chi/v5" ) // RateLimiter implements a simple in-memory rate limiter @@ -79,7 +77,13 @@ func RateLimit(limit int, window time.Duration) func(http.Handler) http.Handler return func(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // Get client IP - ip := chi.URLParam(r, "X-Real-IP") + ip := r.Header.Get("CF-Connecting-IP") + if ip == "" { + ip = r.Header.Get("X-Real-IP") + } + if ip == "" { + ip = r.Header.Get("X-Forwarded-For") + } if ip == "" { ip = r.RemoteAddr } diff --git a/obsidian/Crussell/Crussell Nails.md b/obsidian/Crussell/Crussell Nails.md index 1c981aa..2cf78af 100644 --- a/obsidian/Crussell/Crussell Nails.md +++ b/obsidian/Crussell/Crussell Nails.md @@ -21,6 +21,10 @@ - [x] Login rate limiting (1 attempt per 5 seconds) - [x] Global rate limiting middleware (per-endpoint: 120/min public, 10/min register, 60/min filters, none admin) - [x] Security headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection) +- [ ] **Strict-Transport-Security (HSTS)** - Tell browsers to only access via HTTPS, prevents downgrade attacks. Add after HTTPS is working in prod. +- [ ] **Referrer-Policy** - Track referrer sources for analytics (social media tracking). Use `strict-origin-when-cross-origin` to send origin but not full URLs. +- [ ] **Rate limiter + Cloudflare** - Currently doesn't read CF-Connecting-IP header, so behind Cloudflare all users share one rate limit bucket. +- [ ] **Account creation spam** - Registration endpoint (10/min) could benefit from additional bot protection beyond rate limiting. - [x] Input validation on all endpoints: - Registration: name (1-50), email (255), phone (20), password (72), age 16+ - Services: name (100), price (>0), duration (1-480), patch test (0-168), age (0-100)