From c3026c34b042cc044cddfc5674d5f5ad69bb845d Mon Sep 17 00:00:00 2001 From: grm Date: Fri, 18 Sep 2026 13:50:35 +0300 Subject: Security: Throttle search, cap its words and give the query a deadline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit /search runs an unindexed regular-expression scan over every published post, built from up to fifty ".*"-joined words, for anyone who asks — the cheapest way for a bot to keep Postgres busy. Queries are now cut at eight words (more never improve the answer), each address gets thirty searches and then thirty a minute, and the statement is cancelled after five seconds; a timeout reads as no results and is logged, rather than a 500. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A --- internal/web/server.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) (limited to 'internal/web/server.go') diff --git a/internal/web/server.go b/internal/web/server.go index 6009310..f02dd68 100644 --- a/internal/web/server.go +++ b/internal/web/server.go @@ -26,14 +26,15 @@ type Server struct { 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 + loginLimit *limiter // per client address and per username: 10 guesses, then 10 a minute + searchLimit *limiter // per client address: the search is an unindexed regex scan } // 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), loginLimit: newLimiter(10, 10)} + s := &Server{cfg: cfg, st: st, tpl: newTemplates(cfg.Dev), loginLimit: newLimiter(10, 10), searchLimit: newLimiter(30, 30)} s.root = s.rootRoutes() s.blog = s.subdomainRoutes() return s -- cgit v1.2.3