{"record":{"id":"7050af405c6fa9e8","repo":"BerriAI/litellm","slug":"only-admins-or-team-admins-can-update-projects","errorCode":null,"errorMessage":"Only admins or team admins can update projects","messagePattern":"Only admins or team admins can update projects","errorType":"http","errorClass":"HTTPException","httpStatus":403,"severity":"error","filePath":"enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py","lineNumber":589,"sourceCode":"            )\n\n        # Permission to *edit* the project must be evaluated against the\n        # project's CURRENT team. Sourcing the team from `data.team_id`\n        # would let an admin of any team pass the check by supplying their\n        # own team_id, hijacking the project (VERIA-55).\n        target_team_id = data.team_id or existing_project.team_id\n        target_team_obj = None\n        if target_team_id is not None:\n            target_team_obj = await _validate_team_exists(team_id=target_team_id, prisma_client=prisma_client)\n\n        has_permission = await _check_user_permission_for_project(\n            user_api_key_dict=user_api_key_dict,\n            team_id=existing_project.team_id,\n            prisma_client=prisma_client,\n        )\n\n        if not has_permission:\n            raise HTTPException(\n                status_code=403,\n                detail={\"error\": \"Only admins or team admins can update projects\"},\n            )\n\n        # Reassigning to a different team also requires admin rights on the\n        # destination team — otherwise a team admin could shed projects into\n        # an unsuspecting team's namespace.\n        if data.team_id is not None and data.team_id != existing_project.team_id:\n            can_assign_to_target = await _check_user_permission_for_project(\n                user_api_key_dict=user_api_key_dict,\n                team_id=data.team_id,\n                prisma_client=prisma_client,\n                team_object=(\n                    LiteLLM_TeamTable.model_validate(target_team_obj.model_dump()) if target_team_obj else None\n                ),\n            )\n            if not can_assign_to_target:\n                raise HTTPException(","sourceCodeStart":571,"sourceCodeEnd":607,"githubUrl":"https://github.com/BerriAI/litellm/blob/6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d/enterprise/litellm_enterprise/proxy/management_endpoints/project_endpoints.py#L571-L607","documentation":"PUT /project/update authorizes the caller with _check_user_permission_for_project evaluated against the project's CURRENT team (existing_project.team_id), not the payload's team_id — deliberately, so a team admin cannot hijack a project by supplying their own team_id (VERIA-55). Callers who are neither proxy admins nor admins of the owning team get HTTP 403.","triggerScenarios":"Calling PUT /project/update with a non-admin key, or with a team-admin key for team A while the project belongs to team B (even if data.team_id claims team A).","commonSituations":"Team admins attempting to take over projects by reassigning team_id in the payload, member-level keys used for maintenance automation, or admins operating across teams they do not manage.","solutions":["Use a proxy admin key, or an admin of the project's current owning team","Grant the caller admin rights on the owning team and retry","If reassignment is legitimate, first have an admin of the current team (or a proxy admin) perform the update"],"exampleFix":"# before (admin of team A, project owned by team B)\ncurl -H 'Authorization: Bearer sk-teamA-admin' -X PUT .../project/update -d '{\"project_id\":\"p1\",\"team_id\":\"A\"}'\n# after (proxy admin or team-B admin performs it)\ncurl -H 'Authorization: Bearer sk-admin' -X PUT .../project/update -d '{\"project_id\":\"p1\",...}'","handlingStrategy":"try-catch","validationCode":"# Authorization depends on server-side team membership; pre-check what you can:\nme = await client.get('/user/info')\nif me.user_info.get('user_role') != 'proxy_admin':\n    raise PermissionError('updates require proxy admin or admin of the project owning team')","typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e.status === 403 && /Only admins or team admins can update/.test(e.body?.detail?.error ?? '')) {\n    throw new Error('Not authorized for this project team; escalate to a proxy admin or owning-team admin');\n  }\n  throw e;\n}","preventionTips":["Resolve the project's owning team (GET /project/info) and match it to the caller's admin scope before updating","Never trust client-supplied team_id for authorization decisions in your own tooling; mirror the server's team-of-record logic"],"tags":["litellm","authorization","rbac","security","team","http-403"],"backgroundTag":null,"analyzedSha":"6c2dcb801bf2b75c18f1bb24140e7cf57465cc4d","analyzedAt":"2026-08-15T07:12:03.035Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}