fix: APIKeyManager.load crashes app startup on a corrupt/wrong-shape api_keys.json (#1565)
This commit is contained in:
@@ -48,8 +48,18 @@ class APIKeyManager:
|
|||||||
"""Load and decrypt API keys"""
|
"""Load and decrypt API keys"""
|
||||||
if not os.path.exists(self.api_keys_file):
|
if not os.path.exists(self.api_keys_file):
|
||||||
return {}
|
return {}
|
||||||
with open(self.api_keys_file, 'r', encoding="utf-8") as f:
|
try:
|
||||||
encrypted_keys = json.load(f)
|
with open(self.api_keys_file, 'r', encoding="utf-8") as f:
|
||||||
|
encrypted_keys = json.load(f)
|
||||||
|
except (json.JSONDecodeError, OSError) as e:
|
||||||
|
# A corrupt/truncated api_keys.json must not crash load() (called on
|
||||||
|
# startup via app_initializer) — treat it as no stored keys.
|
||||||
|
logger.warning("Failed to read API keys file: %s", e)
|
||||||
|
return {}
|
||||||
|
if not isinstance(encrypted_keys, dict):
|
||||||
|
# Legacy/wrong shape (e.g. a list) — .items() would raise. Ignore it.
|
||||||
|
logger.warning("API keys file has unexpected shape (%s); ignoring", type(encrypted_keys).__name__)
|
||||||
|
return {}
|
||||||
|
|
||||||
decrypted = {}
|
decrypted = {}
|
||||||
for provider, key in encrypted_keys.items():
|
for provider, key in encrypted_keys.items():
|
||||||
|
|||||||
32
tests/test_api_key_manager_corrupt_load.py
Normal file
32
tests/test_api_key_manager_corrupt_load.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
"""Regression: APIKeyManager.load() must not crash on a corrupt/wrong-shape file.
|
||||||
|
|
||||||
|
load() is called during startup (app_initializer). It had no try/except around
|
||||||
|
`json.load` and called `encrypted_keys.items()` directly, so a corrupt/truncated
|
||||||
|
api_keys.json raised JSONDecodeError and a legacy list-shaped file raised
|
||||||
|
AttributeError — both crashing app startup. It now returns {} instead.
|
||||||
|
"""
|
||||||
|
from src.api_key_manager import APIKeyManager
|
||||||
|
|
||||||
|
|
||||||
|
def _mgr(tmp_path):
|
||||||
|
return APIKeyManager(str(tmp_path))
|
||||||
|
|
||||||
|
|
||||||
|
def test_corrupt_json_returns_empty(tmp_path):
|
||||||
|
(tmp_path / "api_keys.json").write_text("{not valid json", encoding="utf-8")
|
||||||
|
assert _mgr(tmp_path).load() == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_list_shape_returns_empty(tmp_path):
|
||||||
|
(tmp_path / "api_keys.json").write_text('["openai", "anthropic"]', encoding="utf-8")
|
||||||
|
assert _mgr(tmp_path).load() == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_missing_file_returns_empty(tmp_path):
|
||||||
|
assert _mgr(tmp_path).load() == {}
|
||||||
|
|
||||||
|
|
||||||
|
def test_valid_roundtrip(tmp_path):
|
||||||
|
mgr = _mgr(tmp_path)
|
||||||
|
mgr.save("openai", "sk-secret")
|
||||||
|
assert mgr.load() == {"openai": "sk-secret"}
|
||||||
Reference in New Issue
Block a user