odysseus-dev/odysseus · critical · HTTPException
Admin only
Error message
Admin only
What it means
HTTP 403 from the admin middleware. After internal-tool bypasses fail, when AUTH_ENABLED is not 'false' and the app has no configured auth_manager (or it reports is_configured false), every request hitting this middleware is rejected with 'Admin only' because there is no way to establish an admin user.
Source
Thrown at core/middleware.py:53
"""
# In-process bypass for tool-layer loopback calls. Two paths:
# (a) header-direct (caller set X-Odysseus-Internal-Token), or
# (b) the auth middleware already validated the token and stamped
# request.state.current_user = "internal-tool".
try:
hdr = request.headers.get(INTERNAL_TOOL_HEADER)
if hdr and secrets.compare_digest(hdr, INTERNAL_TOOL_TOKEN):
return
if getattr(request.state, "current_user", None) == INTERNAL_TOOL_USER:
return
except Exception:
pass
auth_mgr = getattr(request.app.state, "auth_manager", None)
if os.getenv("AUTH_ENABLED", "true").lower() == "false":
return
if not auth_mgr or not auth_mgr.is_configured:
raise HTTPException(403, "Admin only")
user = getattr(request.state, "current_user", None)
if not user or not auth_mgr.is_admin(user):
raise HTTPException(403, "Admin only")
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
"""Add standard security headers to all responses."""
async def dispatch(self, request: Request, call_next) -> Response:
# Generate a per-request nonce for inline scripts
nonce = secrets.token_hex(16)
request.state.csp_nonce = nonce
response = await call_next(request)
path = request.url.path
# Tool render endpoints
is_tool_render = path.startswith("/api/tools/") and path.endswith("/render")View on GitHub (pinned to f9235ebbf1)
Solutions
- Complete first-run setup so the auth manager is configured with an admin account
- Verify app.state.auth_manager is set and is_configured is true at startup
- For internal tooling, send the INTERNAL_TOOL_HEADER with the correct token (constant-time compared)
- As a last resort in trusted environments, set AUTH_ENABLED=false
Defensive patterns
Strategy: validation
Validate before calling
auth_mgr = getattr(app.state, 'auth_manager', None)
if os.getenv('AUTH_ENABLED', 'true').lower() != 'false' and (not auth_mgr or not auth_mgr.is_configured):
fail_startup('Complete setup or configure the auth manager before serving admin routes') Try / catch
from fastapi import HTTPException
try:
admin_guard(request)
except HTTPException as e:
if e.status_code == 403 and not app.state.auth_manager.is_configured:
redirect('/setup') # guide to first-run setup
raise Prevention
- Fail fast at startup when auth is enabled but unconfigured
- Complete first-run setup before exposing the app
- Use the internal-tool header for trusted automation instead of disabling auth
When it happens
Trigger: AUTH_ENABLED unset/true (default) while the auth manager was never initialized — e.g. first boot without a created account, or auth state failed to load — and an internal-tool header/token is not supplied.
Common situations: Fresh deployment where setup was not completed; auth database/config missing or unreadable; app mounted without the auth middleware wiring app.state.auth_manager.
Related errors
- Error: email MCP requires an authenticated owner or ODYSSEUS
- Authentication required
- HTTP ${res.status}${body ? ': ' + body.slice(0, 160) : ''}
- Failed to update task
- Account creation failed
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/b68a629fb7b7ae82.
Report an issue: GitHub.