Summary
POST /query_multiple returns matching document chunks filtered by file_id only, with no user_id authorization check. Any authenticated caller can read another tenant's document text by supplying that tenant's file_id.
Details
The single-file POST /query enforces ownership — after the similarity search it checks the matched document's user_id against the requester:
if doc_user_id is None or doc_user_id == user_authorized:
authorized_documents = documents
query_embeddings_by_file_ids (/query_multiple) has no equivalent. It filters purely by file_id and returns whatever matched:
documents = await vector_store.asimilarity_search_with_score_by_vector(
embedding, k=body.k, filter={"file_id": {"$in": body.file_ids}}, ...
)
...
return documents
Impact
In a multi-tenant deployment (e.g. LibreChat), a user who learns another user's file_id can retrieve that file's chunk text via /query_multiple, bypassing the per-user isolation that /query enforces.
Fix
Apply the same per-document ownership filter as /query: keep only chunks whose user_id is None (unowned) or matches the authenticated identity. PR to follow.
Summary
POST /query_multiplereturns matching document chunks filtered byfile_idonly, with nouser_idauthorization check. Any authenticated caller can read another tenant's document text by supplying that tenant'sfile_id.Details
The single-file
POST /queryenforces ownership — after the similarity search it checks the matched document'suser_idagainst the requester:query_embeddings_by_file_ids(/query_multiple) has no equivalent. It filters purely byfile_idand returns whatever matched:Impact
In a multi-tenant deployment (e.g. LibreChat), a user who learns another user's
file_idcan retrieve that file's chunk text via/query_multiple, bypassing the per-user isolation that/queryenforces.Fix
Apply the same per-document ownership filter as
/query: keep only chunks whoseuser_idisNone(unowned) or matches the authenticated identity. PR to follow.