diff --git a/plugins/search-postgres/server/PostgresSearchProvider.ts b/plugins/search-postgres/server/PostgresSearchProvider.ts index b82c6c20a1..ede107fca9 100644 --- a/plugins/search-postgres/server/PostgresSearchProvider.ts +++ b/plugins/search-postgres/server/PostgresSearchProvider.ts @@ -651,7 +651,7 @@ export default class PostgresSearchProvider extends BaseSearchProvider { const filter = options.filter; - // Visibility predicate for User contexts. A row is visible if any of: + // A document is visible if any of: // // - direct membership on the document // - group membership on the document @@ -663,12 +663,6 @@ export default class PostgresSearchProvider extends BaseSearchProvider { // - the user is the creator AND the doc has no collection (unplaced // drafts that no membership/collection check can reach) // - // This is load-bearing for authorization. Do not derive the collection - // arm from the filter's collectionId leaves — that's the OR-bypass that - // broke `documents.list`. Authorization for any collectionId referenced - // in the filter has already been enforced by `authorizeFilterFields` in - // the route handler; this clause is the safety net. - // // For Team contexts (share-based search), the caller is privileged and // has done its own authorization — narrow to the filter's collectionId // if specified, otherwise to the team's publicly-permissioned set. diff --git a/server/models/helpers/Filters.ts b/server/models/helpers/Filters.ts index b4a4581d82..f86f606b1a 100644 --- a/server/models/helpers/Filters.ts +++ b/server/models/helpers/Filters.ts @@ -52,9 +52,6 @@ const COMPARISON_OPERATORS = new Set([ * relative-past durations (`-P7D` → `now() - 7 days`) and relative-future * durations (`P7D` → `now() + 7 days`). * - * The duration is regex-validated to contain only `P`, `T`, digits, and the - * unit letters `YMWDHS`, so the literal interpolation is safe by construction. - * * @param duration an ISO 8601 duration, optionally signed with a leading `-`. * @returns a Sequelize literal that resolves to `now() ± interval ''`. * @throws if the input is not a valid ISO 8601 duration. diff --git a/server/routes/api/documents/documents.ts b/server/routes/api/documents/documents.ts index 1b40343e54..96675e4673 100644 --- a/server/routes/api/documents/documents.ts +++ b/server/routes/api/documents/documents.ts @@ -225,9 +225,9 @@ router.post( // Exclude archived docs by default. Suppressed when the caller targets a // specific status, or when their filter already references archivedAt. - const filterMentionsArchivedAt = + const filterIncludesArchivedAt = filter !== undefined && hasFieldInFilter(filter, "archivedAt"); - if (!statusFilter && !filterMentionsArchivedAt) { + if (!statusFilter && !filterIncludesArchivedAt) { where[Op.and].push({ archivedAt: { [Op.eq]: null } }); } @@ -259,14 +259,6 @@ router.post( where[Op.and].push(buildWhere(mapped)); } - // Visibility predicate: a row is visible to the user if it lives in a - // collection they can access, or if it is one of their own unplaced - // drafts (collectionId is null). This is load-bearing for authorization - // — do not skip it based on what the filter "looks like", because under - // OR semantics any apparent narrowing collapses. The only exceptions are - // the backlink lookup (which scopes by `id` to relationship-derived - // documents) and the parent-doc membership escape (which intentionally - // surfaces children of an accessible parent across collection boundaries). if (!backlinkDocumentId && !collectionScopeDropped) { const collectionIds = await user.collectionIds(); where[Op.and].push({