High severity7.1GHSA Advisory· Published May 15, 2026
CVE-2026-45399
CVE-2026-45399
Description
Open WebUI is a self-hosted artificial intelligence platform designed to operate entirely offline. Prior to 0.9.0, any authenticated user with low privileges can enumerate active background tasks across the system and stop tasks belonging to other users via the GET /api/tasks and POST /api/tasks/stop/{task_id} methods. This allows a casual user to disrupt system-wide chat usage by continuously canceling other users' active tasks. This is a real authorization vulnerability affecting integrity and usability in multi-user deployments. This vulnerability is fixed in 0.9.0.
Affected products
1- Range: <= 0.8.12
Patches
1e7ff4768f8fffix: Add ownership checks to global task endpoints (#23454)
3 files changed · +72 −9
backend/open_webui/main.py+29 −6 modified@@ -67,6 +67,7 @@ periodic_session_pool_cleanup, get_event_emitter, get_models_in_use, + get_user_id_from_session_pool, ) from open_webui.routers import ( analytics, @@ -566,6 +567,7 @@ list_task_ids_by_item_id, create_task, stop_task, + stop_item_tasks, list_tasks, ) # Import from tasks.py @@ -1974,7 +1976,7 @@ async def chat_action(request: Request, action_id: str, form_data: dict, user=De @app.post('/api/tasks/stop/{task_id}') -async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_verified_user)): +async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_admin_user)): try: result = await stop_task(request.app.state.redis, task_id) return result @@ -1983,22 +1985,43 @@ async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_ve @app.get('/api/tasks') -async def list_tasks_endpoint(request: Request, user=Depends(get_verified_user)): +async def list_tasks_endpoint(request: Request, user=Depends(get_admin_user)): return {'tasks': await list_tasks(request.app.state.redis)} -@app.get('/api/tasks/chat/{chat_id}') +@app.get('/api/tasks/chat/{chat_id:path}') async def list_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): - chat = await Chats.get_chat_by_id(chat_id) - if chat is None or chat.user_id != user.id: - return {'task_ids': []} + if chat_id.startswith('local:'): + socket_id = chat_id[len('local:'):] + owner_id = get_user_id_from_session_pool(socket_id) + if owner_id != user.id and user.role != 'admin': + return {'task_ids': []} + else: + chat = await Chats.get_chat_by_id(chat_id) + if chat is None or (chat.user_id != user.id and user.role != 'admin'): + return {'task_ids': []} task_ids = await list_task_ids_by_item_id(request.app.state.redis, chat_id) log.debug(f'Task IDs for chat {chat_id}: {task_ids}') return {'task_ids': task_ids} +@app.post('/api/tasks/chat/{chat_id:path}/stop') +async def stop_tasks_by_chat_id_endpoint(request: Request, chat_id: str, user=Depends(get_verified_user)): + if chat_id.startswith('local:'): + socket_id = chat_id[len('local:'):] + owner_id = get_user_id_from_session_pool(socket_id) + if owner_id != user.id and user.role != 'admin': + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) + else: + chat = await Chats.get_chat_by_id(chat_id) + if chat is None or (chat.user_id != user.id and user.role != 'admin'): + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=ERROR_MESSAGES.NOT_FOUND) + result = await stop_item_tasks(request.app.state.redis, chat_id) + return result + + ################################## # # Config Endpoints
src/lib/apis/index.ts+33 −1 modified@@ -273,10 +273,42 @@ export const stopTask = async (token: string, id: string) => { return res; }; +export const stopTasksByChatId = async (token: string, chat_id: string) => { + let error = null; + + const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${encodeURIComponent(chat_id)}/stop`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + ...(token && { authorization: `Bearer ${token}` }) + } + }) + .then(async (res) => { + if (!res.ok) throw await res.json(); + return res.json(); + }) + .catch((err) => { + console.error(err); + if ('detail' in err) { + error = err.detail; + } else { + error = err; + } + return null; + }); + + if (error) { + throw error; + } + + return res; +}; + export const getTaskIdsByChatId = async (token: string, chat_id: string) => { let error = null; - const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${chat_id}`, { + const res = await fetch(`${WEBUI_BASE_URL}/api/tasks/chat/${encodeURIComponent(chat_id)}`, { method: 'GET', headers: { Accept: 'application/json',
src/lib/components/chat/Chat.svelte+10 −2 modified@@ -86,6 +86,7 @@ chatAction, generateMoACompletion, stopTask, + stopTasksByChatId, getTaskIdsByChatId } from '$lib/apis'; import { getTools } from '$lib/apis/tools'; @@ -2464,11 +2465,18 @@ const stopResponse = async (processQueue = true) => { if (taskIds) { - for (const taskId of taskIds) { - const res = await stopTask(localStorage.token, taskId).catch((error) => { + if ($chatId) { + await stopTasksByChatId(localStorage.token, $chatId).catch((error) => { toast.error(`${error}`); return null; }); + } else { + for (const taskId of taskIds) { + const res = await stopTask(localStorage.token, taskId).catch((error) => { + toast.error(`${error}`); + return null; + }); + } } taskIds = null;
Vulnerability mechanics
AI mechanics synthesis has not run for this CVE yet.
References
6- github.com/advisories/GHSA-8jjp-r2w2-4v22ghsaADVISORY
- github.com/open-webui/open-webui/commit/e7ff4768f8ffe1924b4576381c9e45e8a64350e4ghsa
- github.com/open-webui/open-webui/pull/23454ghsa
- github.com/open-webui/open-webui/releases/tag/v0.9.0ghsa
- github.com/open-webui/open-webui/security/advisories/GHSA-8jjp-r2w2-4v22nvd
- nvd.nist.gov/vuln/detail/CVE-2026-45399ghsa
News mentions
0No linked articles in our index yet.