Summary
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.
Details
Open WebUI exposes GET /api/tasks and POST /api/tasks/stop/{task_id} to any verified user. These endpoints operate on a global task namespace and accept raw task_id values without checking whether the task belongs to the current caller.
As a result, a normal authenticated user can enumerate active global task IDs and stop tasks belonging to other users.
Root cause:
- Route authorization is too weak.
In backend/open_webui/main.py, both endpoints only require get_verified_user:
@app.post('/api/tasks/stop/{task_id}')
async def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_verified_user)):
result = await stop_task(request.app.state.redis, task_id)
@app.get('/api/tasks')
async def list_tasks_endpoint(request: Request, user=Depends(get_verified_user)):
return {'tasks': await list_tasks(request.app.state.redis)}
get_verified_user accepts both user and admin roles in backend/open_webui/utils/auth.py.
- The helper operates on a global namespace.
In backend/open_webui/tasks.py, task listing is global:
async def list_tasks(redis):
if redis:
return await redis_list_tasks(redis)
return list(tasks.keys())
In backend/open_webui/tasks.py, task stopping is by raw task_id:
async def stop_task(redis, task_id: str):
if redis:
item_id = await redis.hget(REDIS_TASKS_KEY, task_id)
await redis_send_command(redis, {'action': 'stop', 'task_id': task_id})
await redis_cleanup_task(redis, task_id, item_id or None)
There is no owner check, no user_id check, and no mapping from task_id back to the current caller before stop or cleanup.
This also appears unintended because the codebase already has a scoped route, GET /api/tasks/chat/{chat_id}, which checks whether the chat belongs to the current user before returning task IDs.
Relevant code references:
backend/open_webui/main.py:1975
backend/open_webui/main.py:1984
backend/open_webui/main.py:1989
backend/open_webui/tasks.py:127
backend/open_webui/tasks.py:145
backend/open_webui/utils/auth.py:415
Suggested remediation:
- Store task ownership metadata such as
user_id and chat_id, then enforce owner-only access for non-admin users
- Suggested implementation locations:
backend/open_webui/main.py: add authentication checks for /api/tasks and /api/tasks/stop/{task_id}
backend/open_webui/tasks.py: add support for storing/querying task ownership metadata such as user_id and chat_id, and support owner-scoped listing/stopping
PoC
Preconditions:
- Default
main branch deployment
- Authentication enabled
- Two normal user accounts, or any multi-user deployment where the attacker has one authenticated non-admin account
- At least one task actively running for another user
This does not require any weakened security settings.
PoC objective:
- Show that a non-admin user can see global active task IDs that are not their own
- Show that the same user can stop another user's active task
Reproduction steps:
Step 1. Victim starts a long-running task
Using the UI, User A starts a long response generation or another background task and leaves it running.
Expected security model:
User B should not be able to see or control User A's task.
Step 2. Attacker enumerates global task IDs
Using User B's authenticated token:
curl -i -H "Authorization: Bearer <USER_B_TOKEN>" http://<open-webui-host>/api/tasks
Expected result:
- only User B's own task IDs should be returned, or
- the endpoint should be admin-only
Actual result:
the response returns the global active task list.
Example response shape:
{"tasks":["<task-id-a>","<task-id-b>"]}
This exposes task IDs belonging to other users.
Step 3. Attacker stops a foreign task
Pick a task ID that belongs to User A and send:
curl -i -X POST -H "Authorization: Bearer <USER_B_TOKEN>" http://<open-webui-host>/api/tasks/stop/<FOREIGN_TASK_ID>
Expected result:
403 Forbidden, or
404 Not Found for non-owned tasks, or
- admin-only access
Actual result:
the server accepts the request and attempts to stop the foreign task.
Example response shape:
{"status":true,"message":"Task <FOREIGN_TASK_ID> stopped."}
Step 4. Observe boundary violation
User A's running task is interrupted or disappears from the active task set even though User B does not own it.
What actions become possible that should not be possible:
- enumerate globally active task IDs across users
- cancel another user's in-progress generation or background work
- repeat this for every returned task ID, causing broad cross-user disruption
Copy-paste PoC summary:
- Enumerate all active tasks as a normal non-admin user
curl -s -H "Authorization: Bearer <USER_B_TOKEN>" http://<open-webui-host>/api/tasks
- Stop a task that does not belong to that user
curl -s -X POST -H "Authorization: Bearer <USER_B_TOKEN>" http://<open-webui-host>/api/tasks/stop/<FOREIGN_TASK_ID>
Impact
Type of vulnerability:
broken object-level authorization affecting a global runtime control-plane endpoint.
Who is impacted:
- all users in a multi-user Open WebUI deployment
- any user currently running a background task, especially chat generation tasks
- administrators indirectly, because normal users can disrupt system-wide usage without admin privileges
Direct impact:
- cross-user task ID disclosure
- cross-user task cancellation
Practical impact:
- interruption of long-running chat responses
- interruption of background indexing or ingestion tasks associated with shared runtime jobs
- one ordinary authenticated low-privilege user can continuously poll
/api/tasks and immediately cancel every newly created active task
- with a simple loop or script, this becomes a practical persistent denial-of-service against chat usage for all users on the instance
- in a multi-user deployment, normal users may be unable to complete any chat generation while the attacker continues polling and cancelling tasks
Why severity is meaningful:
- privileges required: low, only an authenticated non-admin account
- scope: cross-user
- impact class: integrity and availability
- exploitation complexity: low once logged in
This is not full account takeover or privilege escalation, but it enables platform-wide operational disruption from a low-privilege account. In practice, sustained exploitation can make chat functionality effectively unusable for other users on the system.
Resolution
Fixed in commit e7ff4768f (#23454, "Add ownership checks to global task endpoints"), first released in v0.9.0 (Apr 2026).
The fix takes a simpler approach than per-task ownership tracking, which would have required a schema change to attribute every task to a user_id:
GET /api/tasks and POST /api/tasks/stop/{task_id} are restricted to admin-only via Depends(get_admin_user). Cross-user enumeration and termination are no longer reachable from a non-admin account.
- A new scoped
POST /api/tasks/chat/{chat_id}/stop endpoint covers the legitimate non-admin use case (a user stopping their own in-progress generation), reusing the same chat-ownership check the existing GET /api/tasks/chat/{chat_id} already enforces.
CVE-2025-63681 was a prior disclosure of the same authorization gap against v0.6.33; the fix in v0.9.0 also resolves that.
Users on >= 0.9.0 are not affected.
References
Summary
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.
Details
Open WebUI exposes
GET /api/tasksandPOST /api/tasks/stop/{task_id}to any verified user. These endpoints operate on a global task namespace and accept rawtask_idvalues without checking whether the task belongs to the current caller.As a result, a normal authenticated user can enumerate active global task IDs and stop tasks belonging to other users.
Root cause:
In
backend/open_webui/main.py, both endpoints only requireget_verified_user:get_verified_useraccepts bothuserandadminroles inbackend/open_webui/utils/auth.py.In
backend/open_webui/tasks.py, task listing is global:In
backend/open_webui/tasks.py, task stopping is by rawtask_id:There is no owner check, no
user_idcheck, and no mapping fromtask_idback to the current caller before stop or cleanup.This also appears unintended because the codebase already has a scoped route,
GET /api/tasks/chat/{chat_id}, which checks whether the chat belongs to the current user before returning task IDs.Relevant code references:
backend/open_webui/main.py:1975backend/open_webui/main.py:1984backend/open_webui/main.py:1989backend/open_webui/tasks.py:127backend/open_webui/tasks.py:145backend/open_webui/utils/auth.py:415Suggested remediation:
user_idandchat_id, then enforce owner-only access for non-admin usersbackend/open_webui/main.py: add authentication checks for/api/tasksand/api/tasks/stop/{task_id}backend/open_webui/tasks.py: add support for storing/querying task ownership metadata such asuser_idandchat_id, and support owner-scoped listing/stoppingPoC
Preconditions:
mainbranch deploymentThis does not require any weakened security settings.
PoC objective:
Reproduction steps:
Step 1. Victim starts a long-running task
Using the UI, User A starts a long response generation or another background task and leaves it running.
Expected security model:
User B should not be able to see or control User A's task.
Step 2. Attacker enumerates global task IDs
Using User B's authenticated token:
Expected result:
Actual result:
the response returns the global active task list.
Example response shape:
{"tasks":["<task-id-a>","<task-id-b>"]}This exposes task IDs belonging to other users.
Step 3. Attacker stops a foreign task
Pick a task ID that belongs to User A and send:
Expected result:
403 Forbidden, or404 Not Foundfor non-owned tasks, orActual result:
the server accepts the request and attempts to stop the foreign task.
Example response shape:
{"status":true,"message":"Task <FOREIGN_TASK_ID> stopped."}Step 4. Observe boundary violation
User A's running task is interrupted or disappears from the active task set even though User B does not own it.
What actions become possible that should not be possible:
Copy-paste PoC summary:
Impact
Type of vulnerability:
broken object-level authorization affecting a global runtime control-plane endpoint.
Who is impacted:
Direct impact:
Practical impact:
/api/tasksand immediately cancel every newly created active taskWhy severity is meaningful:
This is not full account takeover or privilege escalation, but it enables platform-wide operational disruption from a low-privilege account. In practice, sustained exploitation can make chat functionality effectively unusable for other users on the system.
Resolution
Fixed in commit e7ff4768f (#23454, "Add ownership checks to global task endpoints"), first released in v0.9.0 (Apr 2026).
The fix takes a simpler approach than per-task ownership tracking, which would have required a schema change to attribute every task to a
user_id:GET /api/tasksandPOST /api/tasks/stop/{task_id}are restricted to admin-only viaDepends(get_admin_user). Cross-user enumeration and termination are no longer reachable from a non-admin account.POST /api/tasks/chat/{chat_id}/stopendpoint covers the legitimate non-admin use case (a user stopping their own in-progress generation), reusing the same chat-ownership check the existingGET /api/tasks/chat/{chat_id}already enforces.CVE-2025-63681 was a prior disclosure of the same authorization gap against v0.6.33; the fix in v0.9.0 also resolves that.
Users on
>= 0.9.0are not affected.References