From d17232043baf9f78229d585f4d60acc55183e6f6 Mon Sep 17 00:00:00 2001 From: stumpylog <797416+stumpylog@users.noreply.github.com> Date: Thu, 20 Aug 2026 12:14:31 -0700 Subject: [PATCH] docs: correct three stale pointers in the search comments and admin docs _map_emit_error's docstring pointed at the QueryParserError arm of views.py's generic handler, which was deleted; QueryParserError no longer appears in production code at all. _REGEX_TIMEOUT justified itself as ReDoS protection, but the two pre-parse rewrites it was written for are gone and its one remaining use is a character class that cannot backtrack. The timeout stays, its rationale is corrected. administration.md listed two triggers for --if-needed; the schema fingerprint is now a third. Co-Authored-By: Claude Opus 5 --- docs/administration.md | 3 ++- src/documents/search/_query.py | 16 +++++++++------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/docs/administration.md b/docs/administration.md index 4c855b3a2..155716660 100644 --- a/docs/administration.md +++ b/docs/administration.md @@ -521,7 +521,8 @@ Pass `--recreate` to wipe the existing index before rebuilding. Use this when th index is corrupted or you want a fully clean rebuild. Pass `--if-needed` to skip the rebuild if the index is already up to date (schema -version and search language match). Safe to run on every startup or upgrade. +version, schema fingerprint and search language all match). Safe to run on every +startup or upgrade. Specify `optimize` to optimize the index. This command is regularly invoked by the task scheduler. diff --git a/src/documents/search/_query.py b/src/documents/search/_query.py index b8518866d..0a24d37e3 100644 --- a/src/documents/search/_query.py +++ b/src/documents/search/_query.py @@ -26,8 +26,10 @@ if TYPE_CHECKING: logger = logging.getLogger("paperless.search") -# Maximum seconds any single regex substitution may run. -# Prevents ReDoS on adversarial user-supplied query strings. +# Maximum seconds any single regex substitution over user-supplied query text +# may run. The one remaining use is a character class, which cannot backtrack, +# so the bound is an upper limit on that substitution's cost, not the ReDoS +# guard it was originally written as. _REGEX_TIMEOUT: Final[float] = 1.0 # Matches CJK/Hangul characters so queries can be routed to bigram fields. @@ -62,11 +64,11 @@ def _map_emit_error(e: QueryError) -> SearchQueryError: INVALID_INPUT/UNSUPPORTED are user-input errors, exactly like a parse diagnostic, and map to a 400. INTERNAL means a defect in whoosh-compat or in our own AST handling, never the user's query, so the QueryError is - re-raised to surface the same way views.py already lets QueryParserError - surface. MISCONFIGURED is deliberately both: the registry and the index - schema disagree, which only an operator can fix, so it is logged as an - error, but a request is still waiting and the query cannot run either - way, so it also returns a 400. + re-raised rather than converted, reaching the generic 500 handler instead + of blaming the query. MISCONFIGURED is deliberately both: the registry and + the index schema disagree, which only an operator can fix, so it is logged + as an error, but a request is still waiting and the query cannot run + either way, so it also returns a 400. EXISTS_REQUIRES_FAST is the one MISCONFIGURED kind that is not a disagreement. whoosh-compat derives it from the registry's own FieldSpec