{"record":{"id":"e5e0d5c989ffcefe","repo":"odysseus-dev/odysseus","slug":"action-task-action-requires-admin-privileges","errorCode":null,"errorMessage":"Action '{task.action}' requires admin privileges","messagePattern":"Action '(.+?)' requires admin privileges","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"routes/task_routes.py","lineNumber":1064,"sourceCode":"    async def webhook_trigger(task_id: str, token: str):\n        \"\"\"Unauthenticated endpoint — the token IS the auth.\"\"\"\n        db = SessionLocal()\n        try:\n            task = db.query(ScheduledTask).filter(\n                ScheduledTask.id == task_id,\n                ScheduledTask.webhook_token == token,\n                ScheduledTask.status == \"active\",\n            ).first()\n            if not task:\n                raise HTTPException(404, \"Not found\")\n            if (\n                is_admin_only_task_action(task.task_type, task.action)\n                and not owner_has_admin_task_privileges(task.owner)\n            ):\n                task.status = \"paused\"\n                task.next_run = None\n                db.commit()\n                raise HTTPException(403, f\"Action '{task.action}' requires admin privileges\")\n        finally:\n            db.close()\n        started = await task_scheduler.run_task_now(task_id)\n        if not started:\n            raise HTTPException(409, \"Task is already running\")\n        return {\"ok\": True, \"message\": \"Task triggered via webhook\"}\n\n    @router.post(\"/{task_id}/webhook-regenerate\")\n    async def regenerate_webhook(request: Request, task_id: str):\n        user = _owner(request)\n        db = SessionLocal()\n        try:\n            task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()\n            if not task:\n                raise HTTPException(404, \"Task not found\")\n            if user and task.owner != user:\n                raise HTTPException(403, \"Access denied\")\n            task.webhook_token = secrets.token_urlsafe(32)","sourceCodeStart":1046,"sourceCodeEnd":1082,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L1046-L1082","documentation":"HTTP 403 from the unauthenticated webhook trigger when the task's (task_type, action) is classified admin-only by is_admin_only_task_action() but the task owner no longer holds admin privileges (owner_has_admin_task_privileges). As a safety measure the endpoint sets the task to 'paused' and clears next_run before raising, so the privileged action cannot keep firing.","triggerScenarios":"POST /api/tasks/{task_id}/webhook/{token} for a task whose action is in the admin-only set (e.g. a shell/system action) after the owner's admin rights were revoked or the admin flag was never set for that user.","commonSituations":"An admin created a privileged scheduled task, later stepped down or the account flag changed; tasks migrated from a single-admin install into a multi-user deployment where the owner is a normal user.","solutions":["Grant the task owner admin privileges, then manually resume the task (it was paused by this guard)","Change the task's action to a non-admin equivalent so it no longer trips is_admin_only_task_action","Delete and recreate the task under an account that holds admin privileges"],"exampleFix":"# before: task paused + 403 on webhook\n# after: restore privileges and resume\nUPDATE users SET is_admin=1 WHERE username='<task.owner>';\n# then PUT /api/tasks/{id} with {\"status\":\"active\"}","handlingStrategy":"validation","validationCode":"const task = await getTask(taskId);\nif (isAdminOnlyAction(task) && !currentUserIsAdmin(task.owner)) {\n  showWarning('This task requires an admin owner; it will be paused on trigger.');\n}","typeGuard":null,"tryCatchPattern":"if (resp.status === 403) { await resumeTaskAfterPrivilegeFix(taskId); alertOwner(task.owner); }","preventionTips":["Audit scheduled tasks whenever user privileges change","Expect the side effect: the task is paused before the 403 is returned","Keep admin-only actions out of webhook-triggered tasks by design"],"tags":["http","authorization","webhook","tasks","admin"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}