Summary
Workspace search returns data the caller shouldn't be able to see: issues, cycles, modules, views and names from secret projects they aren't a member of, plus the titles of other users' private pages.
Where
SearchService.Search (apps/api/internal/service/search.go) only checks workspace membership (IsMember). The SQL in SearchStore.Search (apps/api/internal/store/search.go) then filters purely on workspace_id:
- Issues/epics/cycles/modules/views join
projects with no visibility predicate, so secret projects (network = 0) are included.
- The pages query filters on
workspace_id, deleted_at and archived_at only. There's no access or owned_by_id check, so private pages (access = 1) owned by other people come back.
- The projects query returns every project name and identifier, secret ones included.
Impact
A plain workspace member (or a guest) who is not on a secret project calls GET /api/workspaces/:slug/search/?q=... and gets back issue titles and IDENT-seq references, cycle/module/view names, secret project names, and other users' private page titles.
This is a separate code path from the secret-project sub-resource issue. Fixing ensureProjectAccess on the sub-resource services does not touch the search SQL, and the private-page-title leak isn't about secret projects at all.
Suggested fix
Scope the search queries the same way the list endpoints do: exclude secret projects the caller isn't a member of (or isn't a workspace admin for), and for pages restrict to access = public OR owned_by_id = caller.
Summary
Workspace search returns data the caller shouldn't be able to see: issues, cycles, modules, views and names from secret projects they aren't a member of, plus the titles of other users' private pages.
Where
SearchService.Search(apps/api/internal/service/search.go) only checks workspace membership (IsMember). The SQL inSearchStore.Search(apps/api/internal/store/search.go) then filters purely onworkspace_id:projectswith no visibility predicate, so secret projects (network = 0) are included.workspace_id,deleted_atandarchived_atonly. There's noaccessorowned_by_idcheck, so private pages (access = 1) owned by other people come back.Impact
A plain workspace member (or a guest) who is not on a secret project calls
GET /api/workspaces/:slug/search/?q=...and gets back issue titles andIDENT-seqreferences, cycle/module/view names, secret project names, and other users' private page titles.This is a separate code path from the secret-project sub-resource issue. Fixing
ensureProjectAccesson the sub-resource services does not touch the search SQL, and the private-page-title leak isn't about secret projects at all.Suggested fix
Scope the search queries the same way the list endpoints do: exclude secret projects the caller isn't a member of (or isn't a workspace admin for), and for pages restrict to
access = public OR owned_by_id = caller.