mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-07-11 12:27:13 +00:00
fix(tasks): scope manage_tasks mutations to an exact task owner (#5264)
The edit/delete/pause/run actions of do_manage_tasks gated ownership with `if owner and task.owner and task.owner != owner`. The middle term made the check a no-op whenever task.owner was null/empty — the state a scheduled task sits in when it was created in no-login mode (or via the localhost middleware bypass) before the periodic legacy-owner sweep reassigns it to the admin user. Any authenticated user's agent could then edit, delete, pause, or run another tenant's owner-less task; edit+run lets an attacker rewrite the task prompt and execute it in the scheduler's agent context. The sibling `list` action already scopes with an exact `owner == owner` filter, so the mutators were strictly more permissive than the reader. Drop the middle term so the guard fails closed on owner-less rows for authenticated callers, matching `list` and the calendar/notes/gallery/session null-owner gates. Auth disabled (owner falsy) and same-owner access are unchanged.
This commit is contained in:
+8
-4
@@ -356,7 +356,11 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
|
||||
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
|
||||
if not task:
|
||||
return {"error": f"Task {task_id} not found", "exit_code": 1}
|
||||
if owner and task.owner and task.owner != owner:
|
||||
# Strict ownership: the old `task.owner and task.owner != owner`
|
||||
# skipped the check on an owner-less task (created in no-login mode
|
||||
# or before the legacy-owner sweep), letting any authenticated user
|
||||
# reach it. `list` already scopes to an exact owner match.
|
||||
if owner and task.owner != owner:
|
||||
return {"error": "Access denied", "exit_code": 1}
|
||||
|
||||
changed = []
|
||||
@@ -402,7 +406,7 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
|
||||
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
|
||||
if not task:
|
||||
return {"error": f"Task {task_id} not found", "exit_code": 1}
|
||||
if owner and task.owner and task.owner != owner:
|
||||
if owner and task.owner != owner:
|
||||
return {"error": "Access denied", "exit_code": 1}
|
||||
name = task.name
|
||||
db.delete(task)
|
||||
@@ -416,7 +420,7 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
|
||||
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
|
||||
if not task:
|
||||
return {"error": f"Task {task_id} not found", "exit_code": 1}
|
||||
if owner and task.owner and task.owner != owner:
|
||||
if owner and task.owner != owner:
|
||||
return {"error": "Access denied", "exit_code": 1}
|
||||
|
||||
if action == "pause":
|
||||
@@ -437,7 +441,7 @@ async def do_manage_tasks(content: str, owner: Optional[str] = None) -> Dict:
|
||||
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
|
||||
if not task:
|
||||
return {"error": f"Task {task_id} not found", "exit_code": 1}
|
||||
if owner and task.owner and task.owner != owner:
|
||||
if owner and task.owner != owner:
|
||||
return {"error": "Access denied", "exit_code": 1}
|
||||
|
||||
from src.event_bus import get_task_scheduler
|
||||
|
||||
Reference in New Issue
Block a user