{"record":{"id":"1daca2d0aed9fbbe","repo":"odysseus-dev/odysseus","slug":"cannot-chain-to-another-user-s-task","errorCode":null,"errorMessage":"Cannot chain to another user's task","messagePattern":"Cannot chain to another user's task","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"routes/task_routes.py","lineNumber":527,"sourceCode":"\n        task_id = str(uuid.uuid4())\n        db = SessionLocal()\n        try:\n            then_task_id = _validate_then_task_id(db, req.then_task_id, user)\n            notifications_enabled = (\n                False if req.task_type == \"action\" and req.notifications_enabled is None\n                else bool(req.notifications_enabled) if req.notifications_enabled is not None\n                else True\n            )\n            # Validate chained task belongs to same owner\n            if req.then_task_id:\n                chain_target = db.query(ScheduledTask).filter(\n                    ScheduledTask.id == req.then_task_id\n                ).first()\n                if not chain_target:\n                    raise HTTPException(400, \"Chained task not found\")\n                if chain_target.owner != user:\n                    raise HTTPException(403, \"Cannot chain to another user's task\")\n            task = ScheduledTask(\n                id=task_id,\n                owner=user,\n                name=name,\n                prompt=req.prompt,\n                task_type=req.task_type,\n                action=req.action,\n                schedule=req.schedule,\n                scheduled_time=req.scheduled_time,\n                scheduled_day=req.scheduled_day,\n                scheduled_date=sched_date,\n                cron_expression=req.cron_expression,\n                trigger_type=req.trigger_type,\n                trigger_event=req.trigger_event,\n                trigger_count=req.trigger_count,\n                trigger_counter=0,\n                next_run=next_run,\n                status=\"active\" if (req.trigger_type in (\"event\", \"webhook\") or next_run) else \"completed\",","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/task_routes.py#L509-L545","documentation":"Ownership guard on task chaining: the chained target task exists but belongs to a different owner than the requesting user, so linking would let one user trigger another's tasks. Returned as HTTP 403.","triggerScenarios":"POST/PUT a task with then_task_id equal to an id owned by another account on a multi-owner instance. The earlier helper only applies the owner filter when user is set, so this explicit owner != user comparison is the authoritative cross-owner block.","commonSituations":"Multi-user deployment where a user discovers/guesses another's task id; admin-created tasks being chained by regular users; shared ids in documentation or exported configs; migrating tasks between accounts leaving stale chain references.","solutions":["Chain only to tasks owned by the same account.","Restrict the client's chain-target picker to the current user's task list (GET /api/tasks already scopes by owner).","If cross-user chaining is a legitimate feature request, it needs an explicit server-side grant mechanism, not removal of this check."],"exampleFix":"// before\nconst target = allTasks.find(t => t.name === 'Admin Cleanup'); // may be another user's\n\n// after\nconst myTasks = await api.listTasks(); // owner-scoped\nconst target = myTasks.find(t => t.name === 'Cleanup');\nif (!target) throw new Error('Pick one of your own tasks');","handlingStrategy":"validation","validationCode":"async function canChainTo(thenTaskId) {\n  const res = await fetch(`/api/tasks/${encodeURIComponent(thenTaskId)}`);\n  if (res.status === 403) return false; // not yours\n  return res.ok;\n}","typeGuard":null,"tryCatchPattern":"try { await api.createTask(p); }\ncatch (e) {\n  if (e.status === 403 && /another user/.test(e.message)) { showOwnTasksOnly(); return; }\n  throw e;\n}","preventionTips":["Source chain targets exclusively from the owner-scoped task list endpoint.","Never accept task ids typed manually by users for chaining.","Test cross-owner chaining with two accounts in CI to confirm 403."],"tags":["authorization","http-403","tasks","chaining","multi-tenant"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}