{"record":{"id":"148ee9b0fea088ce","repo":"odysseus-dev/odysseus","slug":"workspace-browsing-is-admin-only","errorCode":null,"errorMessage":"Workspace browsing is admin-only","messagePattern":"Workspace browsing is admin-only","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"routes/workspace_routes.py","lineNumber":29,"sourceCode":"_MAX_BROWSE_DIRS = 500\n\n\ndef setup_workspace_routes():\n    router = APIRouter(prefix=\"/api/workspace\", tags=[\"workspace\"])\n\n    @router.get(\"/browse\")\n    def browse(request: Request, path: str = Query(default=\"\")):\n        \"\"\"List subdirectories of `path` (default: home) so the UI can navigate\n        the server filesystem and pick a workspace folder. Directories only.\n\n        ADMIN-ONLY: this enumerates the server filesystem, so it is gated the\n        same way the file/shell tools are (read_file/write_file/bash are in\n        NON_ADMIN_BLOCKED_TOOLS). A non-admin who can't use those tools must not\n        be able to map the host's directory tree either.\n        \"\"\"\n        owner = get_current_user(request)\n        if not owner_is_admin_or_single_user(owner):\n            raise HTTPException(status_code=403, detail=\"Workspace browsing is admin-only\")\n\n        # Resolve symlinks so the reported path is canonical and the UI navigates\n        # real directories (defends against symlink games in displayed paths).\n        target = os.path.realpath(os.path.expanduser(path.strip() or \"~\"))\n        if not os.path.isdir(target):\n            target = os.path.realpath(os.path.expanduser(\"~\"))\n\n        dirs = []\n        try:\n            with os.scandir(target) as it:\n                for entry in it:\n                    try:\n                        # Don't follow symlinks when classifying - a symlinked\n                        # dir is skipped rather than letting the browser wander\n                        # off via a link. Hidden entries are omitted.\n                        if entry.is_dir(follow_symlinks=False) and not entry.name.startswith(\".\"):\n                            # Build the child path server-side with os.path.join\n                            # so it's correct on Windows (backslashes) and Linux.","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/workspace_routes.py#L11-L47","documentation":"HTTP 403 from GET /browse (workspace routes): the authenticated user is neither an admin nor the single user of a single-user deployment, so filesystem browsing is denied. The endpoint enumerates server directories (same trust level as read_file/write_file/bash tools, which are in NON_ADMIN_BLOCKED_TOOLS), so any non-admin is blocked from mapping the host directory tree.","triggerScenarios":"A regular multi-user account calling /browse; a user whose admin flag was revoked but whose browser tab still has the workspace picker open; anonymous/session-cookie access without admin role.","commonSituations":"Multi-user installs where the workspace UI is visible to everyone but only functional for admins; permission changes not reflected until re-login.","solutions":["Log in as an admin user (or the single user in single-user mode) to browse workspaces","Have an admin grant the user admin rights if browsing is required for their role","Non-admins: ask an admin to set the workspace path, or use per-user workspace settings if available"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"user = get_current_user(request)\nif not owner_is_admin_or_single_user(user):\n    hide_workspace_picker()  # never call /browse","typeGuard":"def can_browse_workspace(user) -> bool:\n    return owner_is_admin_or_single_user(user)","tryCatchPattern":"if resp.status_code == 403 and 'admin-only' in detail:\n    hide_picker_and_show_notice('Ask an admin to set the workspace')","preventionTips":["Gate the workspace picker UI on the user's admin flag, not just on page load","Re-check role after permission changes / re-login"],"tags":["authorization","admin-only","workspace","http-403"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}