mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-09 03:18:44 +00:00
fix(key-management): block personal key budget delegation from CLI session tokens
When team_table is None (personal key, no team_id in request), the personal key has no team-budget enforcement at request time. A session token therefore cannot delegate any explicit max_budget for a personal key -- that would open a budget bypass path. Block the request with a clear 400 directing the caller to use a team_id instead.
This commit is contained in:
parent
5be0cc9ce6
commit
434072dfaa
2 changed files with 59 additions and 1 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue