odysseus-dev/odysseus · error · HTTPException
Access denied
Error message
Access denied
What it means
Ownership guard on clear-cache: the task exists but task.owner differs from the requesting user, so the cache-clear is refused with HTTP 403. Note the check is 'if user and task.owner != user' — when _owner(request) yields None/anonymous, the guard is skipped, so this fires only for authenticated non-owner users.
Source
Thrown at routes/task_routes.py:582
current user. Anonymous callers get nothing (prevents
cross-tenant drain — see review CRIT-B)."""
user = _owner(request)
if not user:
return {"notifications": []}
notes = task_scheduler.pop_notifications(owner=user)
return {"notifications": notes}
@router.post("/{task_id}/clear-cache")
async def clear_task_cache(request: Request, task_id: str):
"""Clear derived cache for one built-in task."""
user = _owner(request)
db = SessionLocal()
try:
task = db.query(ScheduledTask).filter(ScheduledTask.id == task_id).first()
if not task:
raise HTTPException(404, "Task not found")
if user and task.owner != user:
raise HTTPException(403, "Access denied")
action = task.action or ""
finally:
db.close()
cache_tables = {
"summarize_emails": ("email_summaries",),
"draft_email_replies": ("email_ai_replies",),
"email_auto_translate": ("email_translations",),
"extract_email_events": ("email_calendar_extractions",),
"learn_sender_signatures": ("sender_signatures",),
"check_email_urgency": ("email_tags", "email_urgency_alerts"),
}
tables = cache_tables.get(action)
if not tables:
raise HTTPException(400, "This task has no clearable cache")
import sqlite3
from pathlib import PathView on GitHub (pinned to f9235ebbf1)
Solutions
- Clear caches only on tasks you own.
- Scope the client UI so users only see/can act on their own tasks.
- If an admin must clear another user's cache, use an admin-capable surface rather than this owner-checked route.
Example fix
// before
await api.clearCache(taskIdFromUrl);
// after
const task = await api.getTask(taskIdFromUrl);
if (task.owner !== currentUser) throw new Error('Not your task');
await api.clearCache(taskIdFromUrl); Defensive patterns
Strategy: validation
Validate before calling
const task = await api.getTask(taskId);
if (task.owner !== currentUser) throw new Error('Not your task — cannot clear cache');
await api.clearCache(taskId); Try / catch
try { await api.clearCache(taskId); }
catch (e) {
if (e.status === 403) { showNotice('You can only clear your own task caches'); return; }
throw e;
} Prevention
- Restrict cache-clear UI to tasks where row.owner === current user.
- Refresh identity context (account switcher) together with the task list.
When it happens
Trigger: POST /api/tasks/{id}/clear-cache where id belongs to another owner and the request carries an identifying owner (header/session that _owner resolves to a non-null user).
Common situations: Multi-user instance where cached email-derived tables are per-owner; admin-owned built-in tasks being cache-cleared by a regular account; account switch in the UI leaving an old task id selected.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- Cannot chain to another user's task
- Action '{action}' requires admin privileges
- Failed to update task
- HTTP ${res.status}
- Admin only
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/df2f28d235a936c8.
Report an issue: GitHub.