fix(policy): reject empty specific attachments

This commit is contained in:
Ganni Galea Curmi 2026-05-26 21:08:29 -04:00
parent 73e9071311
commit e777ebeefe
2 changed files with 46 additions and 1 deletions

View file

@ -8,7 +8,7 @@ the final guardrails list.
from datetime import datetime
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, ConfigDict, Field
from pydantic import BaseModel, ConfigDict, Field, model_validator
class PolicyMatchContext(BaseModel):
@ -326,6 +326,19 @@ class PolicyAttachmentCreateRequest(BaseModel):
description="Tag patterns this attachment applies to. Supports wildcards (e.g., health-*).",
)
@model_validator(mode="after")
def validate_scope_has_selector(self) -> "PolicyAttachmentCreateRequest":
if self.scope == "*":
return self
if any((self.teams, self.keys, self.models, self.tags)):
return self
raise ValueError(
"Specific policy attachments must include at least one non-empty selector "
"(teams, keys, models, or tags). Use scope='*' for global attachments."
)
class PolicyAttachmentDBResponse(BaseModel):
"""Response for a policy attachment from the database."""

View file

@ -3,8 +3,10 @@ Tests for pipeline field on policy CRUD types (resolver_types.py).
"""
import pytest
from pydantic import ValidationError
from litellm.types.proxy.policy_engine.resolver_types import (
PolicyAttachmentCreateRequest,
PolicyCreateRequest,
PolicyDBResponse,
PolicyUpdateRequest,
@ -100,3 +102,33 @@ def test_policy_create_request_roundtrip():
dumped = req.model_dump()
restored = PolicyCreateRequest(**dumped)
assert restored.pipeline == pipeline_data
def test_policy_attachment_create_request_rejects_empty_specific_scope():
with pytest.raises(ValidationError, match="at least one non-empty selector"):
PolicyAttachmentCreateRequest(policy_name="pii-policy")
def test_policy_attachment_create_request_rejects_empty_selector_list():
with pytest.raises(ValidationError, match="at least one non-empty selector"):
PolicyAttachmentCreateRequest(policy_name="pii-policy", teams=[])
def test_policy_attachment_create_request_allows_explicit_global_scope():
request = PolicyAttachmentCreateRequest(
policy_name="pii-policy",
scope="*",
)
assert request.scope == "*"
@pytest.mark.parametrize("selector_field", ["teams", "keys", "models", "tags"])
def test_policy_attachment_create_request_allows_selector_scope(selector_field):
selector_value = f"{selector_field}-a"
request = PolicyAttachmentCreateRequest(
policy_name="pii-policy",
**{selector_field: [selector_value]},
)
assert getattr(request, selector_field) == [selector_value]