Skip to content

fix(search): bound query length before pg-tsquery (ReDoS guard — sweep F6) - #7

Merged
PMQ9 merged 1 commit into
wiki-v2from
fix/security-sweep-f6-search-redos
Sep 2, 2026
Merged

PMQ9 merged 1 commit into
wiki-v2from
fix/security-sweep-f6-search-redos

Conversation

@PMQ9

@PMQ9 PMQ9 commented Sep 2, 2026

Copy link
Copy Markdown

What

Security sweep 2026-09-02 finding F6 (MEDIUM) — unbounded search query → pg-tsquery ReDoS.

[email protected]'s word regex backtracks O(N²), so an unbounded SearchDTO.query (tens of thousands of & then >) blocked Node's event loop for seconds before any DB call — a low-privileged, and via the @Public POST /search/share-search route unauthenticated, availability DoS.

Fix (defense in depth)

  • Fork clamp at the top of PdpSearchService.searchPage, ahead of the super.searchPage(...) delegation, so the one fork-owned choke point covers both the authenticated /search path and the anonymous share path (which reaches the upstream tsquery via super). Rejects >1024-char queries with 400.
  • DTO @MaxLength(MAX_SEARCH_QUERY_LENGTH=1024) on SearchDTO.query and SearchSuggestionDTO.query (SearchShareDTO inherits) — the global ValidationPipe rejects at the edge for every search route. Documented as upstream seam fix(auth): __Host- the Docmost session cookie to stop sibling-origin shadowing (cccvu/wiki-v2#310) #42 in the superproject's UPSTREAM_MODIFICATIONS.md.
  • Both bounds share the exported MAX_SEARCH_QUERY_LENGTH so they can't drift.

Tests

authz/search/pdp-search.service.spec.ts — over-length rejection on both the authenticated and unauthenticated public-share paths, inclusive boundary (length === 1024 proceeds), a max-length backtracking run parses fast (no event-loop block), and @MaxLength DTO validation for all three DTOs. All 24 authz/search tests pass.

Paired with the superproject PR (F3/F4/F6). Boundary check + all fork search specs green.

🤖 Generated with Claude Code

…p F6)

pg-tsquery 8.4.2's word regex backtracks O(N^2), so an unbounded SearchDTO.query
(tens of thousands of `&` then `>`) blocked Node's event loop for seconds BEFORE
any DB call — a low-privileged, and via @public /search/share-search UNAUTHENTICATED,
availability DoS.

- Fork clamp at the TOP of PdpSearchService.searchPage, ahead of the
  `super.searchPage` delegation, so the ONE fork choke point covers both the
  authenticated /search path and the anonymous share path (which reaches the
  upstream tsquery via super) — rejects >1024-char queries with 400.
- DTO @maxlength(MAX_SEARCH_QUERY_LENGTH=1024) on SearchDTO.query and
  SearchSuggestionDTO.query (SearchShareDTO inherits) — global ValidationPipe
  rejects at the edge for every search route. Documented as upstream seam #42.
- Both bounds share the exported MAX_SEARCH_QUERY_LENGTH so they can't drift.
- Tests: over-length rejection on authed + public paths, inclusive boundary,
  fast parse of a max-length backtracking run, and DTO @maxlength validation.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
@PMQ9
PMQ9 force-pushed the fix/security-sweep-f6-search-redos branch from d3071b7 to 643f1e2 Compare September 2, 2026 18:35
@PMQ9
PMQ9 merged commit db180b4 into wiki-v2 Sep 2, 2026
@PMQ9
PMQ9 deleted the fix/security-sweep-f6-search-redos branch September 2, 2026 19:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant