aboutsummaryrefslogtreecommitdiffstats
path: root/internal/web/search.go
diff options
context:
space:
mode:
authorgrm <grm@eyesin.space>2026-09-18 13:50:35 +0300
committergrm <grm@eyesin.space>2026-09-18 13:50:42 +0300
commitc3026c34b042cc044cddfc5674d5f5ad69bb845d (patch)
tree7111e8fc310b193c510110e813befb00695a7597 /internal/web/search.go
parentc47397ac1e2ceafafe2be3cdec86366dd396ed6f (diff)
downloadblogspace-c3026c34b042cc044cddfc5674d5f5ad69bb845d.tar.gz
blogspace-c3026c34b042cc044cddfc5674d5f5ad69bb845d.tar.bz2
blogspace-c3026c34b042cc044cddfc5674d5f5ad69bb845d.zip
Security: Throttle search, cap its words and give the query a deadline
/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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Sd8UPWrvyYCLj97JexNw3A
Diffstat (limited to 'internal/web/search.go')
-rw-r--r--internal/web/search.go10
1 files changed, 8 insertions, 2 deletions
diff --git a/internal/web/search.go b/internal/web/search.go
index 9af980d..e0c3533 100644
--- a/internal/web/search.go
+++ b/internal/web/search.go
@@ -5,6 +5,7 @@ import (
"html/template"
"regexp"
"strings"
+ "time"
"unicode/utf8"
"github.com/microcosm-cc/bluemonday"
@@ -14,8 +15,10 @@ import (
const (
searchPerPage = 20
- maxSearchRunes = 100 // longer queries are cut; nobody types more on purpose
- snippetContext = 80 // runes kept on each side of the match
+ maxSearchRunes = 100 // longer queries are cut; nobody types more on purpose
+ maxSearchWords = 8 // more ".*" joins only make the regex scan dearer, never the answer better
+ snippetContext = 80 // runes kept on each side of the match
+ searchDeadline = 5 * time.Second // the scan is unindexed; past this it is abuse or a blog too big for it
)
// searchPattern turns what the reader typed into the regular expression both
@@ -23,6 +26,9 @@ const (
// in between", so "go tem" finds "Go templates". Blank → "".
func searchPattern(q string) string {
words := strings.Fields(q)
+ if len(words) > maxSearchWords {
+ words = words[:maxSearchWords]
+ }
for i, w := range words {
words[i] = regexp.QuoteMeta(w)
}