app/api/search/route.ts is a public GET with no rate limiting. It parses query params and calls fetchSearchPosts, so each request is a database query.
Unlike the write routes it has no consumeSharedRateLimit call. The only guard is q.length < 2, which returns an empty array early and is trivially bypassed by sending three characters.
The cost profile is worse than it looks, because search is a filtered query rather than a keyed lookup, so it is one of the more expensive reads in the app to issue in a loop.
Suggested fix
Rate limit by IP. A ceiling generous enough for someone typing in the search box is still far below what a script does.
Also consider a short cache on repeated identical queries, since a trending case tends to produce the same handful of searches from many people.
Acceptance
app/api/search/route.tsis a public GET with no rate limiting. It parses query params and callsfetchSearchPosts, so each request is a database query.Unlike the write routes it has no
consumeSharedRateLimitcall. The only guard isq.length < 2, which returns an empty array early and is trivially bypassed by sending three characters.The cost profile is worse than it looks, because search is a filtered query rather than a keyed lookup, so it is one of the more expensive reads in the app to issue in a loop.
Suggested fix
Rate limit by IP. A ceiling generous enough for someone typing in the search box is still far below what a script does.
Also consider a short cache on repeated identical queries, since a trending case tends to produce the same handful of searches from many people.
Acceptance
GET /api/searchis rate limited per IP