fix(guardrails): persist disable_global_guardrails on keys (#29233)
* fix(guardrails): restore disable_global_guardrails persistence for keys The per-key/team "Disable Global Guardrails" toggle silently stopped working after #17042, which removed `disable_global_guardrails` from the key/team request models and from the premium metadata allowlist. Without those, the UI's top-level field was dropped by pydantic and never folded into key `metadata`, so the runtime gate always read False and global default_on guardrails kept running. Restore the request-model fields (KeyRequestBase, NewTeamRequest, UpdateTeamRequest) and the `LiteLLM_ManagementEndpoint_MetadataFields_Premium` entry so the flag is promoted into metadata again. Because the key edit form always submits the flag (false by default), guard the UI so it is only sent when it actually changed (edit) or is enabled (create) — this keeps the premium gate on enabling intact while not 403-ing non-premium users who edit unrelated key fields, mirroring how guardrails/tags are already stripped. * test(guardrails): cover disable_global_guardrails toggle-off + clarify premium field comment Add a prepare_metadata_fields case asserting `disable_global_guardrails: False` overwrites an existing `True`, and rewrite the PREMIUM_METADATA_FIELDS comment to explain why boolean premium fields are excluded from the empty-value strip loop.
This commit is contained in:
parent
01e83e2537
commit
9918a9c78c
@ -1067,6 +1067,7 @@ class KeyRequestBase(GenerateRequestBase):
|
||||
key: Optional[str] = None
|
||||
budget_id: Optional[str] = None
|
||||
tags: Optional[List[str]] = None
|
||||
disable_global_guardrails: Optional[bool] = None
|
||||
enforced_params: Optional[List[str]] = None
|
||||
allowed_routes: Optional[list] = []
|
||||
allowed_passthrough_routes: Optional[list] = None
|
||||
@ -1832,6 +1833,7 @@ class NewTeamRequest(TeamBase):
|
||||
prompts: Optional[List[str]] = None
|
||||
object_permission: Optional[LiteLLM_ObjectPermissionBase] = None
|
||||
allowed_passthrough_routes: Optional[list] = None
|
||||
disable_global_guardrails: Optional[bool] = None
|
||||
secret_manager_settings: Optional[dict] = None
|
||||
model_rpm_limit: Optional[Dict[str, int]] = None
|
||||
rpm_limit_type: Optional[
|
||||
@ -1900,6 +1902,7 @@ class UpdateTeamRequest(LiteLLMPydanticObjectBase):
|
||||
guardrails: Optional[List[str]] = None
|
||||
policies: Optional[List[str]] = None
|
||||
object_permission: Optional[LiteLLM_ObjectPermissionBase] = None
|
||||
disable_global_guardrails: Optional[bool] = None
|
||||
team_member_budget: Optional[float] = None
|
||||
team_member_budget_duration: Optional[str] = None
|
||||
team_member_rpm_limit: Optional[int] = None
|
||||
@ -4281,6 +4284,7 @@ LiteLLM_ManagementEndpoint_MetadataFields = [
|
||||
]
|
||||
|
||||
LiteLLM_ManagementEndpoint_MetadataFields_Premium = [
|
||||
"disable_global_guardrails",
|
||||
"guardrails",
|
||||
"policies",
|
||||
"tags",
|
||||
|
||||
@ -853,6 +853,18 @@ def test_personal_key_generation_check():
|
||||
{"tags": ["old_tag"]},
|
||||
{"metadata": {"tags": ["old_tag"], "enforced_params": ["metadata.tags"]}},
|
||||
),
|
||||
(
|
||||
{"disable_global_guardrails": True},
|
||||
{},
|
||||
{},
|
||||
{"metadata": {"disable_global_guardrails": True}},
|
||||
),
|
||||
(
|
||||
{"disable_global_guardrails": False},
|
||||
{},
|
||||
{"disable_global_guardrails": True},
|
||||
{"metadata": {"disable_global_guardrails": False}},
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_prepare_metadata_fields(
|
||||
|
||||
@ -439,6 +439,12 @@ const CreateKey: React.FC<CreateKeyProps> = ({ team, teams, data, addKey, autoOp
|
||||
// Update the formValues with the final metadata
|
||||
formValues.metadata = JSON.stringify(metadata);
|
||||
|
||||
// disable_global_guardrails is premium-gated server-side; only send it when enabled
|
||||
// so non-premium key creation isn't blocked by that gate.
|
||||
if (!formValues.disable_global_guardrails) {
|
||||
delete formValues.disable_global_guardrails;
|
||||
}
|
||||
|
||||
// Transform allowed_vector_store_ids and allowed_mcp_servers_and_groups into object_permission format
|
||||
if (formValues.allowed_vector_store_ids && formValues.allowed_vector_store_ids.length > 0) {
|
||||
formValues.object_permission = {
|
||||
|
||||
@ -34,8 +34,15 @@ interface KeyInfoViewProps {
|
||||
backButtonText?: string;
|
||||
}
|
||||
|
||||
// Must stay in sync with LiteLLM_ManagementEndpoint_MetadataFields_Premium
|
||||
// in litellm/proxy/_types.py — limited to fields the key-edit form submits.
|
||||
// Premium fields (from LiteLLM_ManagementEndpoint_MetadataFields_Premium in
|
||||
// litellm/proxy/_types.py) that the key-edit form submits as arrays/strings, where
|
||||
// "empty" means "unset". The loop below drops them when they're empty-and-were-empty
|
||||
// so a non-premium edit of unrelated fields doesn't trip the server's premium gate.
|
||||
//
|
||||
// Boolean premium fields (e.g. disable_global_guardrails) do NOT belong here: false is
|
||||
// a real value, not "empty", so isEmptyValue(false) is false and the loop would never
|
||||
// drop it — we'd resend false on every edit and trip the gate. Booleans get their own
|
||||
// "send only when changed" guard instead (see disable_global_guardrails below).
|
||||
const PREMIUM_METADATA_FIELDS = [
|
||||
"policies",
|
||||
"guardrails",
|
||||
@ -174,6 +181,15 @@ export default function KeyInfoView({
|
||||
}
|
||||
}
|
||||
|
||||
// disable_global_guardrails is premium-gated server-side; only send it when it
|
||||
// changed so a non-premium edit of unrelated fields isn't blocked by that gate.
|
||||
const previousDisableGlobalGuardrails = Boolean(
|
||||
(currentKeyData.metadata as Record<string, unknown> | undefined)?.disable_global_guardrails,
|
||||
);
|
||||
if (Boolean(formValues.disable_global_guardrails) === previousDisableGlobalGuardrails) {
|
||||
delete formValues.disable_global_guardrails;
|
||||
}
|
||||
|
||||
// Handle max budget empty string
|
||||
formValues.max_budget = mapEmptyStringToNull(formValues.max_budget);
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user