BerriAI/litellm · error · HTTPException

User does not have permission to update this user. Only PROX

Error message

User does not have permission to update this user. Only PROXY_ADMIN can update other users.

What it means

On /user/update, when the target user row exists, can_user_call_user_update allows the call only if the caller is proxy_admin or the target is the caller's own user_id; anything else returns 403. Note the detail is a JSON object {'error': ...} rather than a plain string. Team admins are NOT exempt on this user-management endpoint - only self-updates and proxy admins pass.

Source

Thrown at litellm/proxy/management_endpoints/internal_user_endpoints.py:1266

                )
            )
    except Exception as audit_error:
        verbose_proxy_logger.warning("Failed to create audit log for user %s: %s", response.get("user_id"), audit_error)


def _check_user_update_authz(
    user_request: UpdateUserRequest,
    user_api_key_dict: UserAPIKeyAuth,
    existing_user_row: BaseModel | None,
) -> None:
    """Authorization checks for /user/update — raises HTTPException on failure."""
    if user_request.user_role is not None and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
        raise HTTPException(status_code=403, detail="Only proxy admins can modify user roles.")

    if existing_user_row is not None:
        typed_row: Final = LiteLLM_UserTable.model_validate(existing_user_row.model_dump(exclude_none=True))
        if not can_user_call_user_update(user_api_key_dict=user_api_key_dict, user_info=typed_row):
            raise HTTPException(
                status_code=403,
                detail={
                    "error": "User does not have permission to update this user. Only PROXY_ADMIN can update other users."
                },
            )
    elif user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value:
        # Silent-create guard: only PROXY_ADMIN may create via /user/update.
        raise HTTPException(
            status_code=404,
            detail={
                "error": "User not found. Only PROXY_ADMIN can create users via /user/update; use /user/new instead."
            },
        )


async def _invalidate_user_spend_counter_if_changed(
    non_default_values: Mapping[str, object],
) -> None:

View on GitHub (pinned to 77b7c6c40c)

Solutions

  1. Authenticate with a proxy_admin key to update other users
  2. Non-admins: only send updates for their own user_id (or omit user_id to target self)
  3. For team-scoped management, use the team member management endpoints or have an admin perform user updates

Example fix

# before: member key updating a teammate
POST /user/update -H 'Authorization: Bearer sk-member1' {"user_id": "member2", "max_budget": 10}  # 403

# after: admin key
POST /user/update -H 'Authorization: Bearer sk-admin' {"user_id": "member2", "max_budget": 10}  # 200
Defensive patterns

Strategy: validation

Validate before calling

import requests

def can_update_target(base_url: str, key: str, target_user_id: str) -> bool:
    r = requests.get(f"{base_url}/key/info", params={"key": key}, timeout=10)
    r.raise_for_status()
    info = r.json()["info"]
    return info.get("user_role") == "proxy_admin" or info.get("user_id") == target_user_id

Type guard

def updateWillPassAuthz(keyRole: string, keyUserId: string, targetUserId: string): boolean {
  return keyRole === "proxy_admin" || keyUserId === targetUserId;
}

Try / catch

except requests.HTTPError as e:
    body = e.response.text if e.response is not None else ""
    if e.response is not None and e.response.status_code == 403 and "does not have permission" in body:
        # target is another user: re-issue with admin key or skip
        ...

Prevention

When it happens

Trigger: POST /user/update with the user_id or user_email of another user while authenticated as internal_user or team_admin; scripts that enumerate and update many users with a single member key.

Common situations: Team admins assuming they can edit team members through /user/update; frontends reusing one member's key for everyone's profile saves; shared service keys used for HR-style bulk edits.

Related errors


AI-assisted analysis of BerriAI/litellm@77b7c6c40c (2026-08-18). Data as JSON: /api/errors/2326cfc10a720c6f. Report an issue: GitHub.