diff --git a/litellm/proxy/management_endpoints/key_management_endpoints.py b/litellm/proxy/management_endpoints/key_management_endpoints.py index 719ed503ec8..fca38834d72 100644 --- a/litellm/proxy/management_endpoints/key_management_endpoints.py +++ b/litellm/proxy/management_endpoints/key_management_endpoints.py @@ -747,7 +747,24 @@ async def _common_key_generation_helper( ) # Session tokens (lite login) carry max_budget=None to avoid a per-session # LLM spend cap, but that None must not be read as "unlimited delegation - # authority". Use the team budget as the effective ceiling instead. + # authority". A personal key (no team) has no team-budget enforcement at + # request time, so a session token cannot delegate any budget for one. + if ( + user_api_key_dict.is_session_token + and user_api_key_dict.user_role != LitellmUserRoles.PROXY_ADMIN.value + and not is_ui_session_team_key + and _requested_max_budget is not None + and team_table is None + ): + raise HTTPException( + status_code=400, + detail={ + "error": ( + f"max_budget ({_requested_max_budget}) cannot be set without " + "specifying team_id when using a CLI session token." + ) + }, + ) delegation_ceiling = ( user_api_key_dict.max_budget if user_api_key_dict.max_budget is not None diff --git a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py index a3725e55075..7ff064e659e 100644 --- a/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py +++ b/tests/test_litellm/proxy/management_endpoints/test_key_management_endpoints.py @@ -12636,3 +12636,44 @@ async def test_regular_unlimited_user_delegation_ceiling_not_applied(): team_table=team, ) assert result is not None + + +@pytest.mark.asyncio +async def test_cli_session_token_personal_key_with_budget_blocked(): + caller = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="user-1", + team_id="team-1", + is_session_token=True, + ) + with pytest.raises(HTTPException) as exc_info: + await _common_key_generation_helper( + data=GenerateKeyRequest(max_budget=1000.0), + user_api_key_dict=caller, + litellm_changed_by=None, + team_table=None, # no team in request = personal key + ) + assert exc_info.value.status_code == 400 + assert "team_id" in str(exc_info.value.detail) + + +@pytest.mark.asyncio +async def test_cli_session_token_personal_key_without_budget_allowed(): + caller = UserAPIKeyAuth( + user_role=LitellmUserRoles.INTERNAL_USER, + user_id="user-1", + team_id="team-1", + is_session_token=True, + ) + with patch( + "litellm.proxy.management_endpoints.key_management_endpoints.generate_key_helper_fn", + new_callable=AsyncMock, + return_value={"key": "sk-test", "expires": None, "user_id": "user-1"}, + ): + result = await _common_key_generation_helper( + data=GenerateKeyRequest(max_budget=None), + user_api_key_dict=caller, + litellm_changed_by=None, + team_table=None, # no team in request = personal key, but no explicit budget + ) + assert result is not None