mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-22 07:03:58 +08:00
fix: Round 2 review findings — async SSRF, error sanitization, constants
Security: - Make validate_endpoint async with hass.async_add_executor_job for DNS resolution - Use _RestrictedIPError instead of fragile string matching for IP check flow - Add is_multicast/is_unspecified to SSRF IP restriction checks - Remove resolved private IP from error messages (generic message) - Remove raw endpoint from __init__.py error log - Sanitize error messages in metrics: strip URLs, API keys, Gemini key patterns - Truncate API error response bodies before logging (512 chars) - Use generic error messages in Gemini exception handlers (no str(e) interpolation) - Pass api_key as explicit APIClient constructor parameter (not from header) - Add defensive validation: Gemini provider requires api_key at construction Code quality: - Add constants: DEFAULT_INSTANCE_NAME, MIN/MAX_CONTEXT_MESSAGES, MIN/MAX_HISTORY_SIZE - Replace all hardcoded schema ranges with named constants - Move datetime import to module level in history.py - Remove unused full_history/full_history_available keys - Chain socket.gaierror properly with raise...from
This commit is contained in:
@@ -39,6 +39,7 @@ class APIClient:
|
||||
api_provider: str,
|
||||
model: str,
|
||||
api_timeout: int = DEFAULT_API_TIMEOUT,
|
||||
api_key: Optional[str] = None,
|
||||
) -> None:
|
||||
"""Initialize API client."""
|
||||
self.session = session
|
||||
@@ -48,6 +49,9 @@ class APIClient:
|
||||
self.model = model
|
||||
self.api_timeout = api_timeout
|
||||
self.timeout = ClientTimeout(total=api_timeout)
|
||||
self._api_key = api_key
|
||||
if self.api_provider == API_PROVIDER_GEMINI and not api_key:
|
||||
raise ValueError("Gemini provider requires api_key parameter")
|
||||
self._closed = False
|
||||
|
||||
async def __aenter__(self):
|
||||
@@ -119,7 +123,8 @@ class APIClient:
|
||||
raise HomeAssistantError("API rate limit exceeded")
|
||||
|
||||
# Client/server errors — don't retry
|
||||
_LOGGER.error("API error (status %d): %s", response.status, error_data)
|
||||
truncated_error = str(error_data)[:512]
|
||||
_LOGGER.error("API error (status %d): %s", response.status, truncated_error)
|
||||
raise HomeAssistantError(f"API error: status {response.status}")
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
@@ -368,8 +373,7 @@ class APIClient:
|
||||
|
||||
genai = await asyncio.to_thread(import_genai)
|
||||
|
||||
# Extract API key from headers (Bearer token)
|
||||
api_key = self.headers.get("Authorization", "").replace("Bearer ", "")
|
||||
api_key = self._api_key
|
||||
|
||||
def create_client():
|
||||
if self.endpoint and self.endpoint != "https://generativelanguage.googleapis.com/v1beta":
|
||||
@@ -502,11 +506,11 @@ class APIClient:
|
||||
}
|
||||
|
||||
except ImportError as e:
|
||||
_LOGGER.error(f"Google Gemini library not installed: {str(e)}")
|
||||
raise HomeAssistantError(f"Missing dependency: {str(e)}. Please install google-genai.")
|
||||
_LOGGER.error("Google Gemini library not installed: %s", e)
|
||||
raise HomeAssistantError("Missing dependency: google-genai. Please install it.")
|
||||
except Exception as e:
|
||||
_LOGGER.error(f"Gemini API error: {str(e)}")
|
||||
raise HomeAssistantError(f"Gemini API error: {str(e)}")
|
||||
_LOGGER.error("Gemini API error: %s", e)
|
||||
raise HomeAssistantError("Gemini API request failed")
|
||||
|
||||
async def shutdown(self) -> None:
|
||||
"""Shutdown API client."""
|
||||
|
||||
Reference in New Issue
Block a user