{"record":{"id":"f804c5e8742f578e","repo":"odysseus-dev/odysseus","slug":"workspace-selection-is-admin-only","errorCode":null,"errorMessage":"Workspace selection is admin-only","messagePattern":"Workspace selection is admin-only","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"warning","filePath":"routes/workspace_routes.py","lineNumber":80,"sourceCode":"            \"truncated\": truncated,\n            # Whether this directory may be bound as a workspace (filesystem\n            # roots and sensitive dirs may be browsed through but not chosen).\n            \"selectable\": vet_workspace(target) is not None,\n        }\n\n    @router.get(\"/vet\")\n    def vet(request: Request, path: str = Query(default=\"\")):\n        \"\"\"Validate a workspace path without binding it.\n\n        The UI calls this before persisting a manually typed path (/workspace\n        set) so a typo, file path, deleted folder, sensitive dir, or filesystem\n        root is rejected up front with the canonical path returned on success,\n        instead of being stored client-side and silently dropped at chat time.\n        Admin-gated like /browse: it confirms path existence on the host.\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 selection is admin-only\")\n        from src.tool_execution import vet_workspace\n        resolved = vet_workspace(path)\n        return {\"ok\": resolved is not None, \"path\": resolved}\n\n    return router\n","sourceCodeStart":62,"sourceCodeEnd":86,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/workspace_routes.py#L62-L86","documentation":"HTTP 403 from GET /vet: same admin gate as /browse. The vet endpoint validates a candidate workspace path (existence, sensitivity checks) on the host filesystem, which confirms path existence to the caller — hence admin-only. Non-admin users cannot pre-validate typed paths and must go through the normal (admin-controlled) workspace flow.","triggerScenarios":"A non-admin user's UI calling /vet before /workspace set with a manually typed path; automated scripts using a regular user's session cookie against the vet route.","commonSituations":"The settings page shows the path picker to non-admins in multi-user deployments; permission downgrades while the page stays open.","solutions":["Use an admin session for path vetting/selection","Hide the manual path input for non-admins in the UI so /vet is never called","Non-admins: accept the workspace assigned by the admin instead of a custom path"],"exampleFix":null,"handlingStrategy":"type-guard","validationCode":"if not owner_is_admin_or_single_user(get_current_user(request)):\n    disable_manual_path_input()  # /vet is never called for non-admins","typeGuard":"def can_vet_workspace(user) -> bool:\n    return owner_is_admin_or_single_user(user)","tryCatchPattern":"if resp.status_code == 403:\n    fall_back_to_admin_set_workspace()","preventionTips":["Only render the manual path field for admins","Non-admins should select from admin-approved workspaces only"],"tags":["authorization","admin-only","workspace","http-403"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}