Harden security controls from REPORT findings
This commit is contained in:
@@ -144,6 +144,87 @@ class AppSettingsProviderResilienceTests(unittest.TestCase):
|
||||
app_settings.update_app_settings(providers=[provider_update])
|
||||
write_settings_mock.assert_not_called()
|
||||
|
||||
def test_sanitize_settings_migrates_legacy_plaintext_api_key_to_encrypted_field(self) -> None:
|
||||
"""Legacy plaintext API keys are still readable and emitted with encrypted storage representation."""
|
||||
|
||||
payload = {
|
||||
"providers": [
|
||||
{
|
||||
"id": "secure-provider",
|
||||
"label": "Secure Provider",
|
||||
"provider_type": "openai_compatible",
|
||||
"base_url": "https://api.openai.com/v1",
|
||||
"timeout_seconds": 45,
|
||||
"api_key": "legacy-plaintext-secret",
|
||||
}
|
||||
],
|
||||
"tasks": {
|
||||
app_settings.TASK_OCR_HANDWRITING: {"provider_id": "secure-provider"},
|
||||
app_settings.TASK_SUMMARY_GENERATION: {"provider_id": "secure-provider"},
|
||||
app_settings.TASK_ROUTING_CLASSIFICATION: {"provider_id": "secure-provider"},
|
||||
},
|
||||
}
|
||||
|
||||
with patch.object(app_settings, "_derive_provider_api_key_key", return_value=b"k" * 32):
|
||||
sanitized = app_settings._sanitize_settings(payload)
|
||||
|
||||
provider = sanitized["providers"][0]
|
||||
self.assertEqual(provider["api_key"], "legacy-plaintext-secret")
|
||||
self.assertTrue(
|
||||
str(provider.get("api_key_encrypted", "")).startswith(
|
||||
f"{app_settings.PROVIDER_API_KEY_CIPHERTEXT_PREFIX}:"
|
||||
)
|
||||
)
|
||||
|
||||
def test_serialize_settings_for_storage_excludes_plaintext_api_key(self) -> None:
|
||||
"""Storage payload serialization persists encrypted provider API keys only."""
|
||||
|
||||
payload = _sample_current_payload()
|
||||
payload["providers"][0]["api_key"] = "storage-secret"
|
||||
payload["providers"][0]["api_key_encrypted"] = ""
|
||||
|
||||
with patch.object(app_settings, "_derive_provider_api_key_key", return_value=b"s" * 32):
|
||||
storage_payload = app_settings._serialize_settings_for_storage(payload)
|
||||
|
||||
provider_storage = storage_payload["providers"][0]
|
||||
self.assertNotIn("api_key", provider_storage)
|
||||
self.assertTrue(
|
||||
str(provider_storage.get("api_key_encrypted", "")).startswith(
|
||||
f"{app_settings.PROVIDER_API_KEY_CIPHERTEXT_PREFIX}:"
|
||||
)
|
||||
)
|
||||
|
||||
def test_read_handwriting_provider_settings_revalidates_dns(self) -> None:
|
||||
"""OCR runtime provider settings enforce DNS revalidation before creating outbound clients."""
|
||||
|
||||
runtime_payload = {
|
||||
"provider": {
|
||||
"id": "openai-default",
|
||||
"provider_type": "openai_compatible",
|
||||
"base_url": "https://api.openai.com/v1",
|
||||
"timeout_seconds": 45,
|
||||
"api_key": "runtime-secret",
|
||||
},
|
||||
"task": {
|
||||
"enabled": True,
|
||||
"model": "gpt-4.1-mini",
|
||||
"prompt": "prompt",
|
||||
},
|
||||
}
|
||||
with (
|
||||
patch.object(app_settings, "read_task_runtime_settings", return_value=runtime_payload),
|
||||
patch.object(
|
||||
app_settings,
|
||||
"normalize_and_validate_provider_base_url",
|
||||
return_value="https://api.openai.com/v1",
|
||||
) as normalize_mock,
|
||||
):
|
||||
runtime_settings = app_settings.read_handwriting_provider_settings()
|
||||
|
||||
normalize_mock.assert_called_once_with("https://api.openai.com/v1", resolve_dns=True)
|
||||
self.assertEqual(runtime_settings["openai_base_url"], "https://api.openai.com/v1")
|
||||
self.assertEqual(runtime_settings["openai_api_key"], "runtime-secret")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user