Summary
Multi-user session isolation is broken. Every authenticated user can enumerate and access all other users' chat sessions via the API and WebSocket endpoints.
Affected versions
v1.4.3 through v1.4.12 (the "real multi-user isolation" release and all subsequent versions).
Steps to reproduce
- Start DeepTutor with auth enabled (SQLite or PocketBase backend).
- Create two user accounts (e.g.
admin and user2).
- Log in as
admin, create a chat session with some messages.
- Log in as
user2, call GET /api/v1/sessions or open the home page.
- Observe that
admin's sessions appear in user2's session list.
- Any session ID from the list can be used to
GET /api/v1/sessions/{id} and read full conversation history.
Expected behavior
Each user should only see their own sessions. list_sessions should return only sessions belonging to the authenticated user, and get_session/{id} should return 404 for sessions owned by another user.
Actual behavior
All session queries return every session in the database regardless of the authenticated user.
Root cause
SQLite backend (deeptutor/services/session/sqlite_store.py):
- The
sessions table has no user_id column (schema at lines 100-108).
list_sessions (line 1110), get_session (line 422), and create_session (line 385) never filter by or store user_id.
PocketBase backend (deeptutor/services/session/pocketbase_store.py):
- The
user_id field exists in the schema (scripts/pb_setup.py line 95) but is required: false, never populated on create, and never used in filters.
list_sessions (line 186), get_session (line 95) have no user_id filter.
- All PocketBase collection RBAC rules (
listRule, viewRule, etc.) are empty strings (lines 103-107), providing no access control.
API layer (deeptutor/api/routers/sessions.py):
- Endpoints authenticate the user and set the
ContextVar, but never pass user_id to the session store.
The v1.4.3 commit (452de667) added per-user filesystem isolation (workspaces, grants, sandbox scoping) but did not add data-level isolation for session records.
What needs to change
- Add
user_id column to the SQLite sessions table (with migration for existing DBs).
- Populate
user_id in create_session() using the current user from get_current_user() / ContextVar.
- Filter all session queries by
user_id:
list_sessions: WHERE user_id = ?
get_session, get_session_with_messages: WHERE id = ? AND user_id = ?
update_session_title, delete_session, etc.: same
- Update
SessionStoreProtocol to accept user_id in relevant methods.
- Set PocketBase RBAC rules:
listRule/viewRule = user_id = @request.auth.id.
Severity
High — any authenticated user can read all other users' conversation history, including any sensitive content they may have shared.
Summary
Multi-user session isolation is broken. Every authenticated user can enumerate and access all other users' chat sessions via the API and WebSocket endpoints.
Affected versions
v1.4.3 through v1.4.12 (the "real multi-user isolation" release and all subsequent versions).
Steps to reproduce
adminanduser2).admin, create a chat session with some messages.user2, callGET /api/v1/sessionsor open the home page.admin's sessions appear inuser2's session list.GET /api/v1/sessions/{id}and read full conversation history.Expected behavior
Each user should only see their own sessions.
list_sessionsshould return only sessions belonging to the authenticated user, andget_session/{id}should return 404 for sessions owned by another user.Actual behavior
All session queries return every session in the database regardless of the authenticated user.
Root cause
SQLite backend (
deeptutor/services/session/sqlite_store.py):sessionstable has nouser_idcolumn (schema at lines 100-108).list_sessions(line 1110),get_session(line 422), andcreate_session(line 385) never filter by or storeuser_id.PocketBase backend (
deeptutor/services/session/pocketbase_store.py):user_idfield exists in the schema (scripts/pb_setup.pyline 95) but isrequired: false, never populated on create, and never used in filters.list_sessions(line 186),get_session(line 95) have nouser_idfilter.listRule,viewRule, etc.) are empty strings (lines 103-107), providing no access control.API layer (
deeptutor/api/routers/sessions.py):ContextVar, but never passuser_idto the session store.The v1.4.3 commit (
452de667) added per-user filesystem isolation (workspaces, grants, sandbox scoping) but did not add data-level isolation for session records.What needs to change
user_idcolumn to the SQLitesessionstable (with migration for existing DBs).user_idincreate_session()using the current user fromget_current_user()/ContextVar.user_id:list_sessions:WHERE user_id = ?get_session,get_session_with_messages:WHERE id = ? AND user_id = ?update_session_title,delete_session, etc.: sameSessionStoreProtocolto acceptuser_idin relevant methods.listRule/viewRule=user_id = @request.auth.id.Severity
High — any authenticated user can read all other users' conversation history, including any sensitive content they may have shared.