fix(proxy): keep WIF secret pointers unresolved when expanding os.environ references

This commit is contained in:
mateo-berri 2026-08-29 14:40:22 -07:00
parent 142092ae87
commit f2240df6da
6 changed files with 109 additions and 5 deletions

View file

@ -698,6 +698,7 @@ from litellm.types.router import (
RouterGeneralSettings,
RoutingPlugin,
SearchToolTypedDict,
holds_secret_pointer,
updateDeployment,
)
from litellm.types.router import ModelInfo as RouterModelInfo
@ -4497,7 +4498,7 @@ class ProxyConfig:
if isinstance(item, dict):
item = self._check_for_os_environ_vars(config=item, depth=depth + 1, max_depth=max_depth)
# if the value is a string and starts with "os.environ/" - then it's an environment variable
elif isinstance(value, str) and value.startswith("os.environ/"):
elif isinstance(value, str) and value.startswith("os.environ/") and not holds_secret_pointer(key):
resolved = get_secret(value)
if resolved is None and secret_manager_would_be_consulted(value):
verbose_proxy_logger.warning("%s is absent from the configured secret manager", value)
@ -5530,7 +5531,7 @@ class ProxyConfig:
for model in model_list:
### LOAD FROM os.environ/ ###
for k, v in model["litellm_params"].items():
if isinstance(v, str) and v.startswith("os.environ/"):
if isinstance(v, str) and v.startswith("os.environ/") and not holds_secret_pointer(k):
model["litellm_params"][k] = get_secret(v)
validate_deployment_max_agentic_loops(model)
validate_deployment_complexity_router_placement(model)
@ -5926,7 +5927,7 @@ class ProxyConfig:
for model in model_list:
### LOAD FROM os.environ/ ###
for k, v in model["litellm_params"].items():
if isinstance(v, str) and v.startswith("os.environ/"):
if isinstance(v, str) and v.startswith("os.environ/") and not holds_secret_pointer(k):
model["litellm_params"][k] = get_secret(v)
## check if they have model-id's ##
@ -5954,7 +5955,11 @@ class ProxyConfig:
return value
decrypted_value: Final = decrypt_value_helper(value=value, key=key, return_original_value=True)
if isinstance(decrypted_value, str) and decrypted_value.startswith("os.environ/"):
if (
isinstance(decrypted_value, str)
and decrypted_value.startswith("os.environ/")
and not holds_secret_pointer(key)
):
return get_secret(decrypted_value)
return decrypted_value

View file

@ -217,6 +217,7 @@ from litellm.types.router import (
RoutingStrategy,
SearchToolTypedDict,
TaggedPreRoutingStrategy,
holds_secret_pointer,
)
from litellm.types.services import ServiceTypes
from litellm.types.utils import (
@ -8757,7 +8758,7 @@ class Router:
## check if litellm params in os.environ
if isinstance(_litellm_params, dict):
for k, v in _litellm_params.items():
if isinstance(v, str) and v.startswith("os.environ/"):
if isinstance(v, str) and v.startswith("os.environ/") and not holds_secret_pointer(k):
_litellm_params[k] = get_secret(v)
_model_info: dict = model.pop("model_info", {})

View file

@ -329,6 +329,17 @@ def anthropic_wif_fields_named(keys: Container[str]) -> tuple[str, ...]:
return tuple(name for name in _anthropic_wif_litellm_params if name in keys)
_ANTHROPIC_WIF_POINTER_FIELDS: Final = frozenset(
name for name in _anthropic_wif_litellm_params if name.endswith("_ref")
)
def holds_secret_pointer(param_name: str) -> bool:
"""A ``*_ref`` federation field is a secret POINTER the identity source dereferences at use
time, so a loader expanding ``os.environ/`` values must leave it as written."""
return param_name in _ANTHROPIC_WIF_POINTER_FIELDS
_RESERVED_INIT_KEYS: Final = frozenset({"self", "params", "__class__"})

View file

@ -11756,3 +11756,54 @@ async def test_load_config_router_authorizes_fallback_targets_against_the_callin
router, _, _ = await ProxyConfig().load_config(router=None, config_file_path=str(config_file))
assert router.fallback_access_check is router_fallback_access_check
def test_resolve_db_litellm_param_keeps_wif_secret_pointers(monkeypatch):
from litellm.proxy.proxy_server import ProxyConfig
monkeypatch.setenv("WIF_TEST_KC_SECRET", "kc-secret")
proxy_config = ProxyConfig()
pointer = proxy_config._resolve_db_litellm_param(
"anthropic_keycloak_client_secret_ref", "os.environ/WIF_TEST_KC_SECRET"
)
dereferenced = proxy_config._resolve_db_litellm_param("api_key", "os.environ/WIF_TEST_KC_SECRET")
assert pointer == "os.environ/WIF_TEST_KC_SECRET"
assert dereferenced == "kc-secret"
@pytest.mark.asyncio
async def test_load_config_keeps_wif_secret_pointers_on_config_models(tmp_path, monkeypatch):
from litellm.proxy.proxy_server import ProxyConfig
monkeypatch.setenv("WIF_TEST_SIGNING_KEY", "-----BEGIN PRIVATE KEY-----")
monkeypatch.setenv("WIF_TEST_FDRL", "fdrl_from_env")
config_file = tmp_path / "config.yaml"
config_file.write_text(
yaml.dump(
{
"model_list": [
{
"model_name": "claude-wif",
"litellm_params": {
"model": "anthropic/claude-haiku-4-5",
"anthropic_federation_rule_id": "os.environ/WIF_TEST_FDRL",
"anthropic_identity_source": "internal_issuer",
"anthropic_issuer_url": "https://litellm.example",
"anthropic_issuer_audience": "https://api.anthropic.com",
"anthropic_issuer_signing_key_ref": "os.environ/WIF_TEST_SIGNING_KEY",
},
}
]
}
)
)
_router, model_list, _general_settings = await ProxyConfig().load_config(
router=None, config_file_path=str(config_file)
)
litellm_params = model_list[0]["litellm_params"]
assert litellm_params["anthropic_federation_rule_id"] == "fdrl_from_env"
assert litellm_params["anthropic_issuer_signing_key_ref"] == "os.environ/WIF_TEST_SIGNING_KEY"

View file

@ -11564,3 +11564,28 @@ class TestTierParamsTheTargetAccepts:
accepted = router._tier_params_the_target_accepts("no-such-group", {"reasoning_effort": "max"}, {})
assert accepted == {"reasoning_effort": "max"}
def test_router_keeps_wif_secret_pointers_unresolved(monkeypatch):
monkeypatch.setenv("WIF_TEST_KC_SECRET", "kc-secret")
monkeypatch.setenv("WIF_TEST_FDRL", "fdrl_from_env")
router = Router(
model_list=[
{
"model_name": "claude-wif",
"litellm_params": {
"model": "anthropic/claude-haiku-4-5",
"anthropic_federation_rule_id": "os.environ/WIF_TEST_FDRL",
"anthropic_identity_source": "keycloak",
"anthropic_keycloak_token_url": "https://keycloak.example/token",
"anthropic_keycloak_client_id": "litellm",
"anthropic_keycloak_client_secret_ref": "os.environ/WIF_TEST_KC_SECRET",
},
}
]
)
litellm_params = router.get_model_list()[0]["litellm_params"]
assert litellm_params["anthropic_federation_rule_id"] == "fdrl_from_env"
assert litellm_params["anthropic_keycloak_client_secret_ref"] == "os.environ/WIF_TEST_KC_SECRET"

View file

@ -8,6 +8,7 @@ from litellm.types.router import (
ModelInfo,
anthropic_wif_fields_named,
anthropic_wif_fields_present,
holds_secret_pointer,
)
from litellm.types.utils import (
CustomPricingLiteLLMParams,
@ -146,3 +147,13 @@ def test_anthropic_wif_fields_named_reports_keys_whatever_their_value():
def test_anthropic_wif_fields_named_is_derived_from_the_shared_list():
assert set(anthropic_wif_fields_named(frozenset(anthropic_wif_litellm_params))) == set(anthropic_wif_litellm_params)
@pytest.mark.parametrize("param_name", ["anthropic_issuer_signing_key_ref", "anthropic_keycloak_client_secret_ref"])
def test_wif_ref_fields_hold_secret_pointers(param_name: str):
assert holds_secret_pointer(param_name)
@pytest.mark.parametrize("param_name", ["api_key", "anthropic_federation_rule_id", "anthropic_identity_token"])
def test_dereferenced_fields_do_not_hold_secret_pointers(param_name: str):
assert not holds_secret_pointer(param_name)