mirror of
https://github.com/BerriAI/litellm.git
synced 2026-10-10 03:28:53 +00:00
fix(proxy): reject out-of-range alerting_args on config field update
Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
3bfc77ac18
commit
73d77e719b
3 changed files with 86 additions and 6 deletions
|
|
@ -39,7 +39,7 @@ from typing import (
|
|||
import anyio
|
||||
import websockets
|
||||
import websockets.exceptions
|
||||
from pydantic import BaseModel, Json, JsonValue
|
||||
from pydantic import BaseModel, Json, JsonValue, ValidationError
|
||||
from typing_extensions import NotRequired, assert_never
|
||||
|
||||
from litellm._uuid import uuid
|
||||
|
|
@ -16172,6 +16172,16 @@ async def update_config_general_settings(
|
|||
detail={"error": f"Invalid type of field value={type(data.field_value)} passed in."},
|
||||
)
|
||||
|
||||
if data.field_name == "alerting_args":
|
||||
try:
|
||||
SlackAlertingArgs.model_validate(data.field_value)
|
||||
except ValidationError as e:
|
||||
errors: Final = "; ".join(f"{'.'.join(str(loc) for loc in err['loc'])}: {err['msg']}" for err in e.errors())
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail={"error": f"Invalid alerting_args: {errors}"},
|
||||
)
|
||||
|
||||
## get general settings from db
|
||||
db_general_settings: Final = await _config_param_table(prisma_client).find_first(
|
||||
where={"param_name": "general_settings"}
|
||||
|
|
|
|||
|
|
@ -10319,6 +10319,75 @@ async def test_update_config_general_settings_emits_audit_log(monkeypatch):
|
|||
assert before["some_api_key"] != "sk-stored-secret"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_config_field_rejects_out_of_range_alerting_args(monkeypatch):
|
||||
"""Out-of-range alerting_args must be rejected at save time. If they land in the
|
||||
DB, SlackAlertingArgs raises during the config reload and alerting breaks."""
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from fastapi import HTTPException
|
||||
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
from litellm.proxy._types import ConfigFieldUpdate
|
||||
from litellm.proxy.proxy_server import update_config_general_settings
|
||||
|
||||
monkeypatch.setattr(proxy_server_module, "prisma_client", MagicMock())
|
||||
|
||||
admin = UserAPIKeyAuth(
|
||||
api_key="hashed-admin",
|
||||
user_id="admin-1",
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN,
|
||||
)
|
||||
with pytest.raises(HTTPException) as exc_info:
|
||||
await update_config_general_settings(
|
||||
data=ConfigFieldUpdate(
|
||||
field_name="alerting_args",
|
||||
field_value={
|
||||
"daily_spend_per_user_threshold": -5.0,
|
||||
"user_spend_check_interval": 20,
|
||||
},
|
||||
config_type="general_settings",
|
||||
),
|
||||
user_api_key_dict=admin,
|
||||
)
|
||||
|
||||
assert exc_info.value.status_code == 400
|
||||
error_msg = exc_info.value.detail["error"]
|
||||
assert "daily_spend_per_user_threshold" in error_msg
|
||||
assert "user_spend_check_interval" in error_msg
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_config_field_accepts_valid_alerting_args(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
from litellm.proxy._types import ConfigFieldUpdate
|
||||
from litellm.proxy.proxy_server import update_config_general_settings
|
||||
|
||||
fake = _fake_prisma_with_config({})
|
||||
monkeypatch.setattr(proxy_server_module, "prisma_client", fake)
|
||||
monkeypatch.setattr(litellm, "store_audit_logs", False)
|
||||
|
||||
admin = UserAPIKeyAuth(
|
||||
api_key="hashed-admin",
|
||||
user_id="admin-1",
|
||||
user_role=LitellmUserRoles.PROXY_ADMIN,
|
||||
)
|
||||
await update_config_general_settings(
|
||||
data=ConfigFieldUpdate(
|
||||
field_name="alerting_args",
|
||||
field_value={
|
||||
"daily_spend_per_user_threshold": 5.0,
|
||||
"user_spend_check_interval": 60,
|
||||
},
|
||||
config_type="general_settings",
|
||||
),
|
||||
user_api_key_dict=admin,
|
||||
)
|
||||
|
||||
written = json.loads(fake.db.litellm_config.upsert.call_args.kwargs["data"]["update"]["param_value"])
|
||||
assert written["alerting_args"]["daily_spend_per_user_threshold"] == 5.0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_update_config_general_settings_applies_ssrf_globals(monkeypatch):
|
||||
import litellm.proxy.proxy_server as proxy_server_module
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import React, { useState, useEffect } from "react";
|
|||
|
||||
import { alertingSettingsCall, updateConfigFieldSetting } from "../networking";
|
||||
import DynamicForm from "./dynamic_form";
|
||||
import { extractProxyErrorMessage } from "@/lib/http/client";
|
||||
import { toast } from "@/lib/toast";
|
||||
interface alertingSettingsItem {
|
||||
field_name: string;
|
||||
|
|
@ -43,7 +44,7 @@ const AlertingSettings: React.FC<AlertingSettingsProps> = ({ accessToken, premiu
|
|||
setAlertingSettings(updatedSettings);
|
||||
};
|
||||
|
||||
const handleSubmit = (formValues: Record<string, any>) => {
|
||||
const handleSubmit = async (formValues: Record<string, any>) => {
|
||||
if (!accessToken) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -64,18 +65,18 @@ const AlertingSettings: React.FC<AlertingSettingsProps> = ({ accessToken, premiu
|
|||
const mergedFormValues = { ...formValues, ...initialFormValues };
|
||||
const { slack_alerting, ...alertingArgs } = mergedFormValues;
|
||||
try {
|
||||
updateConfigFieldSetting(accessToken, "alerting_args", alertingArgs);
|
||||
await updateConfigFieldSetting(accessToken, "alerting_args", alertingArgs);
|
||||
if (typeof slack_alerting === "boolean") {
|
||||
if (slack_alerting == true) {
|
||||
updateConfigFieldSetting(accessToken, "alerting", ["slack"]);
|
||||
await updateConfigFieldSetting(accessToken, "alerting", ["slack"]);
|
||||
} else {
|
||||
updateConfigFieldSetting(accessToken, "alerting", []);
|
||||
await updateConfigFieldSetting(accessToken, "alerting", []);
|
||||
}
|
||||
}
|
||||
// update value in state
|
||||
toast.success("Wait 10s for proxy to update.");
|
||||
} catch (error) {
|
||||
// do something
|
||||
toast.error(extractProxyErrorMessage(error));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue