From db6a9e889565a5cd8ee89ac8bd41f6a049630f8f Mon Sep 17 00:00:00 2001 From: Tim O'Farrell Date: Wed, 28 Jan 2026 14:09:50 -0800 Subject: [PATCH] Fix broken key migration by decrypting legacy encrypted keys before LiteLLM update (#12657) --- enterprise/storage/lite_llm_manager.py | 9 +++++ enterprise/storage/user_store.py | 34 ++++++++----------- .../tests/unit/test_lite_llm_manager.py | 2 +- 3 files changed, 25 insertions(+), 20 deletions(-) diff --git a/enterprise/storage/lite_llm_manager.py b/enterprise/storage/lite_llm_manager.py index acec8a9660..47f83506b0 100644 --- a/enterprise/storage/lite_llm_manager.py +++ b/enterprise/storage/lite_llm_manager.py @@ -18,6 +18,7 @@ from server.constants import ( get_default_litellm_model, ) from server.logger import logger +from storage.encrypt_utils import decrypt_legacy_value from storage.user_settings import UserSettings from openhands.server.settings import Settings @@ -605,6 +606,13 @@ class LiteLlmManager: logger.warning('LiteLLM API configuration not found') return + try: + # Sometimes the key we get is encrypted - attempt to decrypt. + key = decrypt_legacy_value(key) + except Exception: + # The key was not encrypted + pass + payload = { 'key': key, } @@ -621,6 +629,7 @@ class LiteLlmManager: 'invalid_litellm_key_during_update', extra={ 'user_id': keycloak_user_id, + 'text': response.text, }, ) return diff --git a/enterprise/storage/user_store.py b/enterprise/storage/user_store.py index 267107d660..64df958e5b 100644 --- a/enterprise/storage/user_store.py +++ b/enterprise/storage/user_store.py @@ -420,7 +420,6 @@ class UserStore: # For new sign-ups after migration, user_settings won't exist # Fall back to getting data from org_members - is_new_signup = False if not user_settings: logger.info( 'user_store:downgrade_user:user_settings_not_found_checking_org_members', @@ -443,7 +442,6 @@ class UserStore: return None org_member = org_members[0] - is_new_signup = True # Create a new user_settings entry from OrgMember, User, and Org data # This is needed for new sign-ups who don't have user_settings @@ -465,27 +463,25 @@ class UserStore: extra={'user_id': user_id}, ) - # Get the API keys for LiteLLM downgrade - if is_new_signup: - # For new signups, we already have decrypted values in user_settings - decrypted_user_settings = user_settings - else: - # For migrated users, decrypt the legacy model - kwargs = decrypt_legacy_model( - [ - 'llm_api_key', - 'llm_api_key_for_byor', - 'search_api_key', - 'sandbox_api_key', - ], - user_settings, - ) - decrypted_user_settings = UserSettings(**kwargs) + encrypted_fields = [ + 'llm_api_key', + 'llm_api_key_for_byor', + 'search_api_key', + 'sandbox_api_key', + ] + for field in encrypted_fields: + value = getattr(user_settings, field, None) + if value: + try: + value = decrypt_legacy_value(value) + setattr(user_settings, field, value) + except Exception: + pass await LiteLlmManager.downgrade_entries( str(org.id), user_id, - decrypted_user_settings, + user_settings, ) logger.debug( 'user_store:downgrade_user:done_litellm_downgrade_entries', diff --git a/enterprise/tests/unit/test_lite_llm_manager.py b/enterprise/tests/unit/test_lite_llm_manager.py index a724ebeb62..91b4770f3f 100644 --- a/enterprise/tests/unit/test_lite_llm_manager.py +++ b/enterprise/tests/unit/test_lite_llm_manager.py @@ -654,7 +654,7 @@ class TestLiteLlmManager: # Assert mock_logger.warning.assert_called_once_with( 'invalid_litellm_key_during_update', - extra={'user_id': 'test-user-id'}, + extra={'user_id': 'test-user-id', 'text': 'Unauthorized'}, ) @pytest.mark.asyncio