aboutsummaryrefslogtreecommitdiffstats
path: root/AGENTS.md
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 /AGENTS.md
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 'AGENTS.md')
-rw-r--r--AGENTS.md8
1 files changed, 6 insertions, 2 deletions
diff --git a/AGENTS.md b/AGENTS.md
index 986d21d..5ffd8ba 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -143,7 +143,7 @@ internal/web/ server.go (host router, middleware, render helpers)
- **Hardening** (`web/ratelimit.go`): a per-key token bucket throttles the
anonymous endpoints worth abusing — `loginLimit` on `POST /webadmin`, keyed
by client address *and* by lowercased username (10 at once, then 10 a
- minute each), answered with 429 by `s.throttle` and a log line; failed
+ minute each), `searchLimit` on `GET /search` per address — answered with 429 by `s.throttle` and a log line; failed
logins are logged with the username and address, and the login body is
capped at `maxLoginBody` (64 KB) since it is the one POST outside
`guardPOST`. `s.clientIP` is the peer address, or the last
@@ -481,7 +481,11 @@ internal/web/ server.go (host router, middleware, render helpers)
are literal, case does not matter (`~*` / `(?is)`) and spaces mean
"anything in between", in order. `SearchPublishedPosts` matches it against
`title || '\n' || body_md` of published posts from every page, 20 per page
- (`searchPerPage`); queries are cut at 100 runes. Results are title, date
+ (`searchPerPage`); queries are cut at 100 runes and 8 words
+ (`maxSearchWords`). The scan is unindexed, so the handler is throttled
+ per client address (`searchLimit`, 30 then 30 a minute) and the query
+ runs under `searchDeadline` (5 s): a timeout is logged and shown as no
+ results, not a 500. Results are title, date
and `searchSnippet` (the Markdown around the first match, escaped, the
match in `<mark>`; the body's start when only the title matched). A blank
or unmatched query is a normal 200, not a 404; the pager is the page's