{"id":"PYSEC-2026-2714","summary":"Open WebUI: Low-privilege authenticated users can enumerate and stop global background tasks, causing system-wide chat disruption","details":"### Summary\nAny 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.\n\n\n### Details\nOpen 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.\n\nAs a result, a normal authenticated user can enumerate active global task IDs and stop tasks belonging to other users.\n\nRoot cause:\n\n1. Route authorization is too weak.\n\nIn `backend/open_webui/main.py`, both endpoints only require `get_verified_user`:\n\n```python\n@app.post('/api/tasks/stop/{task_id}')\nasync def stop_task_endpoint(request: Request, task_id: str, user=Depends(get_verified_user)):\n    result = await stop_task(request.app.state.redis, task_id)\n\n@app.get('/api/tasks')\nasync def list_tasks_endpoint(request: Request, user=Depends(get_verified_user)):\n    return {'tasks': await list_tasks(request.app.state.redis)}\n```\n\n`get_verified_user` accepts both `user` and `admin` roles in `backend/open_webui/utils/auth.py`.\n\n2. The helper operates on a global namespace.\n\nIn `backend/open_webui/tasks.py`, task listing is global:\n\n```python\nasync def list_tasks(redis):\n    if redis:\n        return await redis_list_tasks(redis)\n    return list(tasks.keys())\n```\n\nIn `backend/open_webui/tasks.py`, task stopping is by raw `task_id`:\n\n```python\nasync def stop_task(redis, task_id: str):\n    if redis:\n        item_id = await redis.hget(REDIS_TASKS_KEY, task_id)\n        await redis_send_command(redis, {'action': 'stop', 'task_id': task_id})\n        await redis_cleanup_task(redis, task_id, item_id or None)\n```\n\nThere is no owner check, no `user_id` check, and no mapping from `task_id` back to the current caller before stop or cleanup.\n\nThis 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.\n\nRelevant code references:\n- `backend/open_webui/main.py:1975`\n- `backend/open_webui/main.py:1984`\n- `backend/open_webui/main.py:1989`\n- `backend/open_webui/tasks.py:127`\n- `backend/open_webui/tasks.py:145`\n- `backend/open_webui/utils/auth.py:415`\n\nSuggested remediation:\n- Store task ownership metadata such as `user_id` and `chat_id`, then enforce owner-only access for non-admin users\n- Suggested implementation locations:\n  - `backend/open_webui/main.py`: add authentication checks for `/api/tasks` and `/api/tasks/stop/{task_id}`\n  - `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\n\n\n\n### PoC\nPreconditions:\n\n- Default `main` branch deployment\n- Authentication enabled\n- Two normal user accounts, or any multi-user deployment where the attacker has one authenticated non-admin account\n- At least one task actively running for another user\n\nThis does not require any weakened security settings.\n\nPoC objective:\n\n1. Show that a non-admin user can see global active task IDs that are not their own\n2. Show that the same user can stop another user's active task\n\nReproduction steps:\n\n#### Step 1. Victim starts a long-running task\n\nUsing the UI, User A starts a long response generation or another background task and leaves it running.\n\nExpected security model:\nUser B should not be able to see or control User A's task.\n\n#### Step 2. Attacker enumerates global task IDs\n\nUsing User B's authenticated token:\n\n```bash\ncurl -i -H \"Authorization: Bearer \u003cUSER_B_TOKEN\u003e\" http://\u003copen-webui-host\u003e/api/tasks\n```\n\nExpected result:\n\n- only User B's own task IDs should be returned, or\n- the endpoint should be admin-only\n\nActual result:\nthe response returns the global active task list.\n\nExample response shape:\n\n```json\n{\"tasks\":[\"\u003ctask-id-a\u003e\",\"\u003ctask-id-b\u003e\"]}\n```\n\nThis exposes task IDs belonging to other users.\n\n#### Step 3. Attacker stops a foreign task\n\nPick a task ID that belongs to User A and send:\n\n```bash\ncurl -i -X POST -H \"Authorization: Bearer \u003cUSER_B_TOKEN\u003e\" http://\u003copen-webui-host\u003e/api/tasks/stop/\u003cFOREIGN_TASK_ID\u003e\n```\n\nExpected result:\n\n- `403 Forbidden`, or\n- `404 Not Found` for non-owned tasks, or\n- admin-only access\n\nActual result:\nthe server accepts the request and attempts to stop the foreign task.\n\nExample response shape:\n\n```json\n{\"status\":true,\"message\":\"Task \u003cFOREIGN_TASK_ID\u003e stopped.\"}\n```\n\n#### Step 4. Observe boundary violation\n\nUser A's running task is interrupted or disappears from the active task set even though User B does not own it.\n\nWhat actions become possible that should not be possible:\n\n- enumerate globally active task IDs across users\n- cancel another user's in-progress generation or background work\n- repeat this for every returned task ID, causing broad cross-user disruption\n\nCopy-paste PoC summary:\n\n1. Enumerate all active tasks as a normal non-admin user\n\n```bash\ncurl -s -H \"Authorization: Bearer \u003cUSER_B_TOKEN\u003e\" http://\u003copen-webui-host\u003e/api/tasks\n```\n\n2. Stop a task that does not belong to that user\n\n```bash\ncurl -s -X POST -H \"Authorization: Bearer \u003cUSER_B_TOKEN\u003e\" http://\u003copen-webui-host\u003e/api/tasks/stop/\u003cFOREIGN_TASK_ID\u003e\n```\n\n### Impact\nType of vulnerability:\nbroken object-level authorization affecting a global runtime control-plane endpoint.\n\nWho is impacted:\n\n- all users in a multi-user Open WebUI deployment\n- any user currently running a background task, especially chat generation tasks\n- administrators indirectly, because normal users can disrupt system-wide usage without admin privileges\n\nDirect impact:\n\n- cross-user task ID disclosure\n- cross-user task cancellation\n\nPractical impact:\n\n- interruption of long-running chat responses\n- interruption of background indexing or ingestion tasks associated with shared runtime jobs\n- one ordinary authenticated low-privilege user can continuously poll `/api/tasks` and immediately cancel every newly created active task\n- with a simple loop or script, this becomes a practical persistent denial-of-service against chat usage for all users on the instance\n- in a multi-user deployment, normal users may be unable to complete any chat generation while the attacker continues polling and cancelling tasks\n\nWhy severity is meaningful:\n\n- privileges required: low, only an authenticated non-admin account\n- scope: cross-user\n- impact class: integrity and availability\n- exploitation complexity: low once logged in\n\nThis 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.\n\n## Resolution\n\nFixed in commit [e7ff4768f](https://github.com/open-webui/open-webui/commit/e7ff4768f8ffe1924b4576381c9e45e8a64350e4) ([#23454](https://github.com/open-webui/open-webui/pull/23454), \"Add ownership checks to global task endpoints\"), first released in **v0.9.0** (Apr 2026).\n\nThe 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`:\n\n- `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.\n- 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.\n\nCVE-2025-63681 was a prior disclosure of the same authorization gap against v0.6.33; the fix in v0.9.0 also resolves that.\n\nUsers on `\u003e= 0.9.0` are not affected.","aliases":["CVE-2026-45399","GHSA-8jjp-r2w2-4v22"],"modified":"2026-07-13T16:32:16.650219026Z","published":"2026-07-13T15:19:07.598740Z","references":[{"type":"WEB","url":"https://github.com/open-webui/open-webui/security/advisories/GHSA-8jjp-r2w2-4v22"},{"type":"ADVISORY","url":"https://nvd.nist.gov/vuln/detail/CVE-2026-45399"},{"type":"WEB","url":"https://github.com/open-webui/open-webui/pull/23454"},{"type":"WEB","url":"https://github.com/open-webui/open-webui/commit/e7ff4768f8ffe1924b4576381c9e45e8a64350e4"},{"type":"PACKAGE","url":"https://github.com/open-webui/open-webui"},{"type":"WEB","url":"https://github.com/open-webui/open-webui/releases/tag/v0.9.0"},{"type":"PACKAGE","url":"https://pypi.org/project/open-webui"},{"type":"ADVISORY","url":"https://github.com/advisories/GHSA-8jjp-r2w2-4v22"}],"affected":[{"package":{"name":"open-webui","ecosystem":"PyPI","purl":"pkg:pypi/open-webui"},"ranges":[{"type":"ECOSYSTEM","events":[{"introduced":"0"},{"fixed":"0.9.0"}]}],"versions":["0.1.124","0.1.125","0.2.0","0.2.1","0.2.2","0.2.3","0.2.4","0.2.5","0.3.0","0.3.1","0.3.10","0.3.12","0.3.13","0.3.14","0.3.15","0.3.16","0.3.17","0.3.17.dev2","0.3.17.dev3","0.3.17.dev4","0.3.17.dev5","0.3.18","0.3.19","0.3.2","0.3.20","0.3.21","0.3.22","0.3.23","0.3.24","0.3.25","0.3.26","0.3.27","0.3.27.dev1","0.3.27.dev2","0.3.27.dev3","0.3.28","0.3.29","0.3.3","0.3.30","0.3.30.dev1","0.3.30.dev2","0.3.31","0.3.31.dev1","0.3.32","0.3.33","0.3.33.dev1","0.3.34","0.3.35","0.3.4","0.3.5","0.3.6","0.3.7","0.3.8","0.3.9","0.4.0","0.4.0.dev1","0.4.0.dev2","0.4.1","0.4.2","0.4.3","0.4.4","0.4.5","0.4.6","0.4.6.dev1","0.4.7","0.4.8","0.5.0","0.5.0.dev1","0.5.0.dev2","0.5.1","0.5.10","0.5.11","0.5.12","0.5.13","0.5.14","0.5.15","0.5.16","0.5.17","0.5.18","0.5.19","0.5.2","0.5.20","0.5.3","0.5.3.dev1","0.5.4","0.5.5","0.5.6","0.5.7","0.5.8","0.5.9","0.6.0","0.6.1","0.6.10","0.6.11","0.6.12","0.6.13","0.6.14","0.6.15","0.6.16","0.6.18","0.6.19","0.6.2","0.6.20","0.6.21","0.6.22","0.6.23","0.6.24","0.6.25","0.6.26","0.6.26.dev1","0.6.27","0.6.28","0.6.29","0.6.3","0.6.30","0.6.31","0.6.32","0.6.33","0.6.34","0.6.35","0.6.36","0.6.37","0.6.38","0.6.39","0.6.4","0.6.40","0.6.41","0.6.42","0.6.43","0.6.5","0.6.6","0.6.6.dev1","0.6.7","0.6.8","0.6.9","0.7.0","0.7.1","0.7.2","0.8.0","0.8.1","0.8.10","0.8.11","0.8.12","0.8.2","0.8.3","0.8.4","0.8.5","0.8.6","0.8.7","0.8.8","0.8.9"],"database_specific":{"source":"https://github.com/pypa/advisory-database/blob/main/vulns/open-webui/PYSEC-2026-2714.yaml"}}],"schema_version":"1.7.5","severity":[{"type":"CVSS_V3","score":"CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:L/I:N/A:H"}]}