Multi-tenant RAG isolation is only partial. Chat websocket/retrieval can cross company document boundaries. Align with the abc_worker stove-pipe pattern (b13cec8: immutable tenant scope per connection + conversation ownership validation).
Problem
Critical — WebSocket conversation IDOR
Client-supplied conversation_id / email drive identity. get_workspace(conversation_id) loads that conversation's company workspace with no check that the authenticated user owns the conversation. Victim company chunks can enter the LLM context.
Critical — Fail-open vector search search_documents(..., workspace=None) sets Chroma filter: None and searches the entire shared collection.
High — Soft isolation only
One shared Chroma collection; optional workspace_id metadata filter. Deletes/signals do not purge vectors by document_id.
Medium — ConversationDetailView IDOR
GET prompts by conversation_id without user=request.user.
Acceptance criteria
Bind websocket turns to authenticated session/JWT user (not spoofable email alone).
Require conversation.user_id == authenticated_user.id before get_messages / get_workspace / RAG.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Summary
Multi-tenant RAG isolation is only partial. Chat websocket/retrieval can cross company document boundaries. Align with the abc_worker stove-pipe pattern (b13cec8: immutable tenant scope per connection + conversation ownership validation).
Problem
Critical — WebSocket conversation IDOR
Client-supplied
conversation_id/emaildrive identity.get_workspace(conversation_id)loads that conversation's company workspace with no check that the authenticated user owns the conversation. Victim company chunks can enter the LLM context.Critical — Fail-open vector search
search_documents(..., workspace=None)sets Chromafilter: Noneand searches the entire shared collection.High — Soft isolation only
One shared Chroma collection; optional
workspace_idmetadata filter. Deletes/signals do not purge vectors bydocument_id.Medium — ConversationDetailView IDOR
GET prompts by
conversation_idwithoutuser=request.user.Acceptance criteria
conversation.user_id == authenticated_user.idbeforeget_messages/get_workspace/ RAG.filter=None; requireworkspace_id(prefer alsocompany_idin metadata).ConversationDetailViewownership check.get_retriever().conversation_id→ deny, no A chunks in retrieval.References
ChatTenantScopeper WebSocket (Fixes #8)Severity
Critical for multi-tenant confidential RAG.