From f8d90e3d80ec798689be5fdf792e6c1401ad748f Mon Sep 17 00:00:00 2001 From: grm Date: Fri, 18 Sep 2026 13:46:11 +0300 Subject: Security: Throttle login attempts with a per-address, per-account token bucket Nothing stopped a bot from trying passwords against /webadmin as fast as bcrypt would go. A small in-memory limiter (stdlib only, one process) now refuses a login with 429 once an address, or an account, has made ten attempts, and lets one more through every six seconds; keying on both means many addresses guessing one account are throttled too. Refusals are logged. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'internal/web/server.go') diff --git a/internal/web/server.go b/internal/web/server.go index f005b76..12440de 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -25,10 +25,15 @@ type Server struct { tpl *templates root http.Handler blog http.Handler + // Anonymous endpoints worth abusing get a token bucket each (ratelimit.go). + loginLimit *limiter // per client address and per username: 10 guesses, then 10 a minute } +// logf is log.Printf, a variable so tests can silence it. +var logf = log.Printf + func NewServer(cfg *config.Config, st *store.Store) *Server { - s := &Server{cfg: cfg, st: st, tpl: newTemplates(cfg.Dev)} + s := &Server{cfg: cfg, st: st, tpl: newTemplates(cfg.Dev), loginLimit: newLimiter(10, 10)} s.root = s.rootRoutes() s.blog = s.subdomainRoutes() return s -- cgit v1.2.3