odysseus-dev/odysseus · error · HTTPException

Action '{action}' requires admin privileges

Error message

Action '{action}' requires admin privileges

What it means

Raised by the task-creation API when a task of type 'action' names an action classified in ADMIN_ONLY_TASK_ACTIONS (actions that run shell/SSH commands or serve cookbook surfaces) and the requesting owner does not pass owner_has_admin_task_privileges(user). It is a deliberate authorization boundary (review item CRIT-C) returning HTTP 403.

Source

Thrown at routes/task_routes.py:435

                        )
                    resumed += 1
                db.commit()
            finally:
                db.close()
        return {"ok": True, "opened": True, "enabled": bool(prefs.get("tasks_enabled")), "resumed": resumed}

    # Actions that execute shell/SSH commands or cross into admin-only
    # Cookbook serving surfaces — restricted to admins.
    # Non-admin users cannot create tasks with these action types via the
    # API. See review CRIT-C.
    _ADMIN_ONLY_ACTIONS = ADMIN_ONLY_TASK_ACTIONS

    def _is_admin(user: str | None) -> bool:
        return owner_has_admin_task_privileges(user)

    def _require_admin_for_task_action(user: str | None, task_type: str | None, action: str | None) -> None:
        if is_admin_only_task_action(task_type, action) and not _is_admin(user):
            raise HTTPException(403, f"Action '{action}' requires admin privileges")

    def _validate_then_task_id(db, then_task_id: Optional[str], user: Optional[str], current_task_id: Optional[str] = None) -> Optional[str]:
        target_id = (then_task_id or "").strip()
        if not target_id:
            return None
        if current_task_id and target_id == current_task_id:
            raise HTTPException(400, "Task cannot chain to itself")
        q = db.query(ScheduledTask).filter(ScheduledTask.id == target_id)
        if user:
            q = q.filter(ScheduledTask.owner == user)
        target = q.first()
        if not target:
            raise HTTPException(404, "Chained task not found")
        return target.id

    @router.post("")
    async def create_task(request: Request, req: TaskCreate):
        user = _owner(request)

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Run the task under an owner that has admin task privileges, or ask the instance admin to create/own the task.
  2. Filter the client-side action list to exclude ADMIN_ONLY_TASK_ACTIONS for non-admin users so the option is never submitted.
  3. If the deployment is single-user and the owner was wrongly detected, verify how _owner(request) derives the user and that the admin privilege list (owner_has_admin_task_privileges) includes that owner.
  4. If policy allows it, have the admin implement the capability as a safer non-admin action instead of relaxing the check.

Example fix

# before
actions = list(BUILTIN_ACTION_INFO.keys())

# after
from src.task_actions import ADMIN_ONLY_TASK_ACTIONS
is_admin = current_user_has_admin_privileges
actions = [a for a in BUILTIN_ACTION_INFO
           if is_admin or a not in ADMIN_ONLY_TASK_ACTIONS]
Defensive patterns

Strategy: validation

Validate before calling

const isAdmin = await api.getMe().then(u => u?.has_admin_task_privileges ?? false);
function canCreateAction(action, isAdmin) {
  const ADMIN_ONLY = new Set(['action_run_local', 'ssh_command', 'run_script']); // keep in sync with ADMIN_ONLY_TASK_ACTIONS
  return isAdmin || !ADMIN_ONLY.has(action);
}

Try / catch

try {
  await api.createTask(payload);
} catch (e) {
  if (e.status === 403 && /admin privileges/.test(e.message)) {
    showInfo('This action type is restricted to admins.');
    return;
  }
  throw e;
}

Prevention

When it happens

Trigger: POST /api/tasks (or PUT update) with task_type='action' and action in the admin-only set (e.g. action_run_local, ssh_command, run_script) while authenticated as a non-admin owner; the check _require_admin_for_task_action fires before schedule validation.

Common situations: Self-hosted multi-user deployment where a regular user clones an admin's task; frontend offers all BUILTIN_ACTION_INFO entries in the picker without filtering by role; upgrading to a version that newly classifies an action as admin-only and previously-created task edits now fail.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/481ae864638852b7. Report an issue: GitHub.