From 6d4c02a89eb683fbd7ce5a23f73d07a551589baa Mon Sep 17 00:00:00 2001 From: Classic298 <27028174+Classic298@users.noreply.github.com> Date: Mon, 27 Jul 2026 08:08:04 +0200 Subject: [PATCH] refac: owner-bind ephemeral web-search RAG collections (#26706) The web-search-* namespace was the one collection namespace filter_accessible_collections admitted unconditionally for any non-admin user, on both read and write, unlike file-*, user-memory-* and knowledge bases which are owner-scoped. process_web_search now mints these ephemeral per-query collections as web-search-{user.id}-, and the access helper only admits web-search-{requester.id}-* names, so a web-search collection is readable and writable only by the user who created it (admins keep their bypass). The collections hold transient public web-search results and their names are non-enumerable query hashes, so there was no demonstrated cross-user access path; this removes the namespace exception so the per-user scoping the other namespaces enforce also covers web-search. Co-authored-by: rexpository --- backend/open_webui/retrieval/utils.py | 10 +++++----- backend/open_webui/routers/retrieval.py | 3 ++- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/backend/open_webui/retrieval/utils.py b/backend/open_webui/retrieval/utils.py index f816efb8fc..6140d0d45d 100644 --- a/backend/open_webui/retrieval/utils.py +++ b/backend/open_webui/retrieval/utils.py @@ -1267,7 +1267,7 @@ async def filter_accessible_collections( - any name with characters outside [A-Za-z0-9_-] → rejected - file-* → validated via has_access_to_file - user-memory-* → must match user's own memory collection - - web-search-* → ephemeral per-query collections, always allowed + - web-search-* → ephemeral per-query collections, owner-bound to web-search-{user.id}-* - knowledge-bases → always denied (system meta-collection) - everything else → if the name matches a knowledge base, validated via Knowledges.check_access_by_user_id; if no @@ -1302,10 +1302,10 @@ async def filter_accessible_collections( if name == f'user-memory-{user.id}': validated.add(name) elif name.startswith('web-search-'): - # Ephemeral collections created by process_web_search — safe - # to allow because they contain only transient web-search - # results scoped to the requesting user's session. - validated.add(name) + # Ephemeral per-query collections, owner-bound: process_web_search mints + # them as web-search-{user.id}-, so only the creator may read/write. + if name.startswith(f'web-search-{user.id}-'): + validated.add(name) else: # May be a knowledge-base ID or a legacy/ephemeral collection. # If it IS a KB, enforce access control. If no such KB diff --git a/backend/open_webui/routers/retrieval.py b/backend/open_webui/routers/retrieval.py index fb64e51947..4ed980fbf8 100644 --- a/backend/open_webui/routers/retrieval.py +++ b/backend/open_webui/routers/retrieval.py @@ -2657,7 +2657,8 @@ async def process_web_search(request: Request, form_data: SearchForm, user=Depen } else: # Create a single collection for all documents - collection_name = f'web-search-{calculate_sha256_string("-".join(form_data.queries))}'[:63] + # Bind the ephemeral collection to its owner so filter_accessible_collections can scope it per-user. + collection_name = f'web-search-{user.id}-{calculate_sha256_string("-".join(form_data.queries))}'[:63] try: await run_in_threadpool(