{"record":{"id":"1e8b04f5a18a8dc5","repo":"BerriAI/litellm","slug":"f-only-proxy-admins-can-create-administrative-user","errorCode":null,"errorMessage":"f\"Only proxy admins can create administrative users (proxy_admin, proxy_admin_viewer). Attempted to create user with role: {data.user_role}. Your role: {user_api_key_dict.user_role}\"","messagePattern":"f\"Only proxy admins can create administrative users \\(proxy_admin, proxy_admin_viewer\\)\\. Attempted to create user with role: (.+?)\\. Your role: (.+?)\"","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"litellm/proxy/management_endpoints/internal_user_endpoints.py","lineNumber":542,"sourceCode":"        await _check_duplicate_user_email(data.user_email, prisma_client)\n\n        # Check if license is over limit\n        billable_users: Final = await UserRepository(prisma_client).count_billable_users()\n        if billable_users and _license_check.is_over_limit(total_users=billable_users):\n            raise HTTPException(\n                status_code=403,\n                detail=\"License is over limit. Please contact support@berri.ai to upgrade your license.\",\n            )\n\n        # Only proxy admins can create administrative users\n        # Check if user_api_key_dict is actually a UserAPIKeyAuth instance (not a Depends object)\n        # This can happen when the function is called directly in tests\n        if (\n            data.user_role in [LitellmUserRoles.PROXY_ADMIN, LitellmUserRoles.PROXY_ADMIN_VIEW_ONLY]\n            and isinstance(user_api_key_dict, UserAPIKeyAuth)\n            and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN\n        ):\n            raise HTTPException(\n                status_code=403,\n                detail=f\"Only proxy admins can create administrative users (proxy_admin, proxy_admin_viewer). Attempted to create user with role: {data.user_role}. Your role: {user_api_key_dict.user_role}\",\n            )\n\n        _check_permissions_caller_permission(\n            data=data,\n            user_api_key_dict=user_api_key_dict,\n        )\n\n        data_json = data.json()\n        data_json = _update_internal_new_user_params(data_json, data)\n        # Persist the requested grants as their own row and link it, mirroring key/team creation.\n        # generate_key_helper_fn only forwards object_permission_id, so without this the entitlement\n        # the caller sent would be dropped on the floor.\n        data_json = await _set_object_permission(data_json=data_json, prisma_client=prisma_client)\n        _hash_password_in_dict(data_json)\n        teams = data.teams\n        if teams is None:","sourceCodeStart":524,"sourceCodeEnd":560,"githubUrl":"https://github.com/BerriAI/litellm/blob/77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8/litellm/proxy/management_endpoints/internal_user_endpoints.py#L524-L560","documentation":"POST /user/new refuses to create administrative users unless the caller is a proxy admin. If the request body sets user_role to proxy_admin or proxy_admin_viewer while the authenticated key's role (UserAPIKeyAuth.user_role) is not proxy_admin, LiteLLM returns 403. The guard blocks privilege escalation: team admins and internal users must not be able to mint new admins. (The isinstance(user_api_key_dict, UserAPIKeyAuth) condition only relaxes the check for direct test calls that pass a Depends object.)","triggerScenarios":"POST /user/new with body containing \"user_role\": \"proxy_admin\" or \"proxy_admin_viewer\", authenticated with a virtual key whose user is internal_user or team_admin; provisioning scripts that try to create admins with a non-admin service key.","commonSituations":"Automation run with team-admin keys; copy-pasted payloads from docs that include user_role; upgrading LiteLLM to a version where this escalation check was added and previously-working calls now fail with 403.","solutions":["Authenticate with a proxy_admin key (the master key or a key bound to an admin user) for the create call","Create the user without user_role (defaults to internal_user) and have a proxy admin promote them afterwards via POST /user/update","Drop or change user_role in the payload if an administrative user was not intended"],"exampleFix":"# before: team-admin key -> 403\ncurl -X POST http://localhost:4000/user/new -H 'Authorization: Bearer sk-team-admin' -d '{\"user_id\": \"u1\", \"user_role\": \"proxy_admin\"}'\n\n# after: proxy-admin key\ncurl -X POST http://localhost:4000/user/new -H 'Authorization: Bearer sk-master-key' -d '{\"user_id\": \"u1\", \"user_role\": \"proxy_admin\"}'  # 200","handlingStrategy":"validation","validationCode":"import requests\n\ndef assert_can_create_admins(base_url: str, key: str) -> None:\n    r = requests.get(f\"{base_url}/key/info\", params={\"key\": key}, timeout=10)\n    r.raise_for_status()\n    info = r.json()[\"info\"]\n    role = info.get(\"user_role\") or \"internal_user\"\n    if role != \"proxy_admin\":\n        raise PermissionError(f\"key role is {role}; proxy_admin required to create admin users\")","typeGuard":null,"tryCatchPattern":"except requests.HTTPError as e:\n    if e.response is not None and e.response.status_code == 403 and \"Only proxy admins\" in e.response.text:\n        # retry with an admin key, or re-submit without user_role\n        ...","preventionTips":["Run admin-provisioning calls only with the master/admin key, never team or user keys","Keep user_role out of generic onboarding payloads","Assert the caller's role via /key/info before any privileged mutation"],"tags":["litellm","rbac","user-management","admin","privilege-escalation"],"backgroundTag":"rbac-insufficient-permissions","analyzedSha":"77b7c6c40c0c5aa5fbcb1d6a1825ac39ca8829b8","analyzedAt":"2026-08-18T11:44:31.656Z","schemaVersion":2},"datasetVersion":"2026-08-21T18:17:14.833Z"}