From a7c350faee366c4ff1189ecfb1e4e302f4ae6d31 Mon Sep 17 00:00:00 2001 From: yucheng Date: Thu, 8 Oct 2026 01:06:11 +0000 Subject: [PATCH] fix(proxy): encrypt first-write config env vars without PyNaCl instead of refusing the save Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> --- litellm/proxy/proxy_server.py | 4 ---- .../proxy/proxy_server/test_proxy_config.py | 19 ++++++++++--------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/litellm/proxy/proxy_server.py b/litellm/proxy/proxy_server.py index 4ed7712d2b1..31e436d1075 100644 --- a/litellm/proxy/proxy_server.py +++ b/litellm/proxy/proxy_server.py @@ -421,7 +421,6 @@ from litellm.proxy.common_utils.discoverable_model_filter import discoverable_ro from litellm.proxy.common_utils.encrypt_decrypt_utils import ( decrypt_value_helper, encrypt_value_helper, - require_legacy_reader_for, ) from litellm.proxy.common_utils.error_body_call_id import JSON_OBJECT, error_body_call_id, with_call_id from litellm.proxy.common_utils.fips import ( @@ -7795,9 +7794,6 @@ class ProxyConfig: _decrypt_and_set_db_env_variables): this is a write path, and loading values into os.environ is the read path's responsibility. """ - require_legacy_reader_for( - environment_variables.values(), "re-encrypt environment variables for the config save" - ) decrypted_env_vars: Final = self._decrypt_db_variables(environment_variables) return self._encrypt_env_variables( environment_variables=decrypted_env_vars, diff --git a/tests/unit/proxy/proxy_server/test_proxy_config.py b/tests/unit/proxy/proxy_server/test_proxy_config.py index 063ee0d9d15..4f149ad54f0 100644 --- a/tests/unit/proxy/proxy_server/test_proxy_config.py +++ b/tests/unit/proxy/proxy_server/test_proxy_config.py @@ -29,7 +29,6 @@ from pydantic import JsonValue, TypeAdapter, ValidationError import litellm from litellm.proxy._types import CommonProxyErrors, ConfigGeneralSettings from litellm.proxy.common_utils.encrypt_decrypt_utils import ( - LegacyEncryptionUnavailableError, decrypt_value_helper, encrypt_value_helper, ) @@ -3748,9 +3747,7 @@ def test_ProxyConfig__encrypt_env_variables_for_db_idempotent(monkeypatch): assert out == {"A": "ENC[1]", "B": "ENC[2]", "C": "ENC[3]"} -def test_ProxyConfig__encrypt_env_variables_for_db_refuses_without_pynacl_instead_of_dropping_legacy_values( - monkeypatch, -): +def test_ProxyConfig__encrypt_env_variables_for_db_without_pynacl_encrypts_new_plaintext_and_keeps_v3(monkeypatch): monkeypatch.setenv("LITELLM_SALT_KEY", "sk-salt-config-save") monkeypatch.setattr("litellm.proxy.proxy_server.general_settings", {}) v3 = encrypt_value_helper("already-migrated") @@ -3758,10 +3755,12 @@ def test_ProxyConfig__encrypt_env_variables_for_db_refuses_without_pynacl_instea monkeypatch.setitem(sys.modules, "nacl.secret", None) pc = ProxyConfig() - saved = pc._encrypt_env_variables_for_db({"A": v3}) - assert decrypt_value_helper(saved["A"], key="A") == "already-migrated" - with pytest.raises(LegacyEncryptionUnavailableError, match=r"config save.*legacy-encryption"): - pc._encrypt_env_variables_for_db({"A": v3, "B": "plain-or-legacy"}) + saved = pc._encrypt_env_variables_for_db({"A": v3, "B": "first-write-plaintext"}) + assert saved["B"].startswith("v3:gcm:") and saved["B"] != "first-write-plaintext" + assert {k: decrypt_value_helper(v, key=k) for k, v in saved.items()} == { + "A": "already-migrated", + "B": "first-write-plaintext", + } def test_ProxyConfig__encrypt_env_variables_for_db_invalid_raises(): @@ -4748,7 +4747,9 @@ async def test_ProxyConfig__update_config_from_db_keeps_keys_the_config_file_omi @pytest.mark.asyncio @pytest.mark.parametrize("ui_settings_already_synced", [False, True]) -async def test_ProxyConfig_add_deployment_continues_after_null_pass_through_endpoints(monkeypatch, ui_settings_already_synced): +async def test_ProxyConfig_add_deployment_continues_after_null_pass_through_endpoints( + monkeypatch, ui_settings_already_synced +): from litellm.proxy import proxy_server pc = ProxyConfig()