API: Add DoS protection to FTS search#1754
Conversation
32c58a1 to
e12ac59
Compare
MrDirkelz
left a comment
There was a problem hiding this comment.
Review: 1722-api-add-dos-protection-to-the-fts-endpoint
The branch does not compile. npx tsc --noEmit in api/ fails with 4 errors. The PR renamed search → searchWithStats (new return type FtsSearchWithStats = {results, stats}) but only updated 5 of the 8 return statements, and deleted a constant that's still referenced. CI's api typecheck will fail, and every un-fixed path crashes the controller at runtime.
Findings (most severe first)
-
Aux path (User/Redirect search) returns bare array → controller crash — ftsSearch.service.ts:200
searchWithStats does return this.searchAux(...), but searchAux returns FtsSearchResultDto[], not FtsSearchWithStats. Runtime: any types: [User] / types: [Redirect] search returns an array; the controller's const { results, stats } = await …searchWithStats(…) yields stats = undefined, then isExpensiveFtsSearch(undefined) throws TypeError: Cannot read properties of undefined. Compile error TS2739. -
Strict path (matchAllWords / sort) returns bare array → controller crash — ftsSearch.service.ts:389 and :395
Both early returns in the strict block still return [] / the mapped array instead of {results, stats}. Same crash as #1 for every strict/sorted FTS query. Compile errors TS2739. -
FTS_MIN_TRIGRAMS referenced after deletion — ftsSearch.service.ts:533
The diff deleted const FTS_MIN_TRIGRAMS = 3 but searchAux's budget loop still uses it. Compile error TS2304 (Cannot find name 'FTS_MIN_TRIGRAMS') — the aux corpus never got the strict-budget change, so the removal broke it. Decide: keep the floor in aux (restore a local const) or apply the same strict cap there. -
The DoS strike mechanism is effectively dead — ftsSearch.controller.ts:96-100 / ftsSearch.service.ts:285
isExpensiveFtsSearch fires when estimatedCandidateRows >= budget OR candidateRows >= budget. But the new strict budget loop breaks before a trigram pushes the sum over budget, so estimatedCandidateRows is always ≤ budget (only == in the exact-fit case) and candidateRows (actual rows for kept trigrams) tracks it — so neither disjunct realistically reaches >= budget. Net: expensive-query logging and recordStrike almost never trigger. The old FTS_MIN_TRIGRAMS floor was the thing that let candidate rows exceed budget (the case worth striking). Worth confirming this is intended — right now the rate limiter ships but has nothing to bite on. (PLAUSIBLE — design/efficacy, not a crash.)
Test gap
The new specs pass only because the controller spec mocks searchWithStats, and the service spec exercises only the content BM25 path — neither covers the aux path, the strict path, or the real return shape. That's exactly why three compile-breaking return statements slipped through green tests.
Return stats from strict and aux FTS paths, restore the min-trigram budget floor, and cover the missed searchWithStats cases in service specs.
Review summary — 🟡 Approve with nitsEffective, correctly mirrors the
🤖 AI-assisted review summary (Claude Code). Read-only analysis of the PR diff — not a substitute for human review. |
No description provided.