fix: Add ownership checks to global task endpoints (#23454)

* Add ownership checks to global task endpoints

- Restrict GET /api/tasks and POST /api/tasks/stop/{task_id} to admin-only
- Add new scoped POST /api/tasks/chat/{chat_id}/stop endpoint with ownership
  check so regular users can stop their own chat tasks
- Allow admins to access the scoped chat task endpoints alongside owners
- Update frontend to use the new scoped stop endpoint when a chatId is available

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

* Handle temporary (local:) chat IDs in scoped task endpoints

Temporary chats use local:<socketId> as chat_id which doesn't exist in
the DB. The scoped endpoints now skip ownership checks for local: IDs
(they aren't enumerable) and use {chat_id:path} to handle the colon in
the URL path.

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

* Verify session ownership for local: chat IDs and URL-encode chat_id

- For local:<socketId> chat IDs, look up the socket's owner in
  SESSION_POOL and verify it matches the requesting user (or admin)
- URL-encode chat_id in frontend fetch calls to handle special
  characters (colon in local: IDs) safely

https://claude.ai/code/session_01K7zPDvvjRu8AxJ4Br2HhZc

---------

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Classic298
2026-04-12 17:56:43 -05:00
committed by GitHub
co-authored by Claude
parent c47dd7b771
commit e7ff4768f8
3 changed files with 72 additions and 9 deletions
+10 -2
View File
@@ -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;