Files
pad/cmd
xarmian 38c591673f fix(search): scope workspace search to visible items (#1723)
* fix(search): scope workspace search to visible items (BUG-3331)

/api/v1/search is mounted outside RequireWorkspaceAccess and decides
access itself. A caller with no visibility in a named workspace reached
the store with an empty permission filter, which the store read as no
filter at all.

Store: the permission filter is fail-closed. SearchParams gains an
explicit Unrestricted flag; empty CollectionIDs and ItemIDs (nil or not)
now match nothing unless it is set. One helper applies the predicate on
every query path (ref lookup, number lookup, FTS, count, facets) and adds
a match-nothing predicate when a restricted filter is empty.

Handler: searchAccessFor resolves each workspace the way
RequireWorkspaceAccess does (fresh-install window, user-less workspace
token limited to its own workspace, consent allow-list, cookie-only admin
bypass, full and restricted members, grants for non-members), for both a
named workspace and the unscoped fan-out. No access is an empty result,
not a 403.

Collections: a caller who sees a collection only through an item grant
gets item_count and active_item_count computed from the granted items,
not the whole collection.

Co-Authored-By: Claude <noreply@anthropic.com>

* test(search): cover the restricted number lookup, total, mixed fan-out and active count (BUG-3331)

Codex r1 (gpt-6-luna, max scrutiny) found no P0/P1. Its P2 (a restricted
member's direct grant to a soft-deleted collection now hides that
collection's items from search while the item list still shows them) is
search being the stricter of two doors; the item list's side is
BUG-3333. Its test gaps are closed here: the bare-number lookup and the
filtered total for an item-grant guest, a fan-out over workspaces with
different access, and the active count following a closed granted item.

Also: the named-workspace consent check is visible in handleSearch again
(the allow-list coverage guard scans the handler; searchAccessFor still
re-checks), and the now-unused removeString is gone.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(search): check the consent allow-list first in searchAccessFor (BUG-3331)

Codex r2 (no P0/P1): the allow-list check came after the no-user branch,
the reverse of RequireWorkspaceAccess, so a context with a mismatching
allow-list and a matching legacy workspace token would have read its
workspace. No current auth flow produces that combination, but the
order now matches the middleware and the coverage claim is exact.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-10-02 11:18:07 -04:00
..