fix(policy_registry): preserve config-loaded policies across DB sync

When sync_policies_from_db runs after load_policies, it clears _policies
entirely and repopulates from the database. If the DB has no policies
(e.g. config-defined policies were never persisted), all config-loaded
policies are lost and every subsequent request runs without guardrails.

Fix: track which policy names came from the YAML config in a new
_config_policy_names set. On DB sync, start from only the config entries
rather than an empty dict. DB entries with the same name still override
the config version, so operator-managed DB policies continue to win.

Fixes #29416
This commit is contained in:
PRABHU KIRAN VANDRANKI 2026-06-02 10:44:39 -04:00
parent a72414a061
commit 9f26bcc953

View file

@ -9,7 +9,7 @@ by policy_attachments (see AttachmentRegistry).
import json
from datetime import datetime, timezone
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Tuple
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Set, Tuple
from litellm._logging import verbose_proxy_logger
from litellm.types.proxy.policy_engine import (
@ -73,6 +73,7 @@ class PolicyRegistry:
self._policies: Dict[str, Policy] = {}
self._policies_by_id: Dict[str, Tuple[str, Policy]] = {}
self._initialized: bool = False
self._config_policy_names: Set[str] = set()
def load_policies(self, policies_config: Dict[str, Any]) -> None:
"""
@ -84,11 +85,13 @@ class PolicyRegistry:
"""
self._policies = {}
self._policies_by_id = {}
self._config_policy_names = set()
for policy_name, policy_data in policies_config.items():
try:
policy = self._parse_policy(policy_name, policy_data)
self._policies[policy_name] = policy
self._config_policy_names.add(policy_name)
verbose_proxy_logger.debug(f"Loaded policy: {policy_name}")
except Exception as e:
verbose_proxy_logger.error(
@ -533,7 +536,13 @@ class PolicyRegistry:
policy_<uuid> overrides can be resolved without DB access in the hot path.
"""
try:
self._policies = {}
# Preserve config-loaded policies so a DB sync with no results does not wipe them.
# DB entries for the same name will override the config version below.
self._policies = {
name: policy
for name, policy in self._policies.items()
if name in self._config_policy_names
}
production = await self.get_all_policies_from_db(
prisma_client, version_status="production"
)