diff --git a/apps/api/src/services/ai_providers/gemini.py b/apps/api/src/services/ai_providers/gemini.py index a439e8cf..16954a73 100644 --- a/apps/api/src/services/ai_providers/gemini.py +++ b/apps/api/src/services/ai_providers/gemini.py @@ -7,10 +7,15 @@ Google Gemini Cloud API (gemini-2.0-flash) 特性: 穩定快速、JSON 輸出可靠、有費用 ($0.075/1M input) 2026-04-02 ogt: Phase 24-A 從 openclaw.py 抽出 +2026-04-29 ogt + Claude Code: P0 SECRET LEAK 修復 + 發現 prod log 出現完整 API key 明碼(feedback_secret_debug_output_ban 鐵律) + 根因:httpx HTTPStatusError str() 會包含完整 URL(含 ?key=... query string) + 修法:_sanitize_error 移除 URL query string + redact key """ from __future__ import annotations +import re import time import httpx @@ -24,6 +29,19 @@ logger = structlog.get_logger(__name__) settings = get_settings() +# 2026-04-29 ogt + Claude Code: P0 SECRET LEAK +# httpx exception str() 會把完整 URL 含 ?key=AIzaSy... 寫進 error message +# 此 redact 函式在所有 logger 出口先過濾,防止 K8s pod log / Sentry / Telegram +# 任何下游接收端看到 key 明碼 +_KEY_REDACT_PATTERN = re.compile(r"([?&])key=[^&\s'\"]+") + + +def _sanitize_error(error: object) -> str: + """從錯誤訊息移除 ?key=xxx 等敏感 query string""" + msg = str(error) + return _KEY_REDACT_PATTERN.sub(r"\1key=", msg) + + class GeminiProvider: """ Google Gemini Cloud Provider @@ -115,8 +133,12 @@ class GeminiProvider: except Exception as e: latency = (time.perf_counter() - start) * 1000 - logger.warning("gemini_provider_failed", error=str(e), latency_ms=round(latency, 1)) - return AIResult(raw_response="", success=False, provider=self.name, latency_ms=latency, error=str(e)) + # 2026-04-29 ogt + Claude Code: P0 SECRET LEAK 修復 + # 之前 str(e) 會洩漏 URL 中的 ?key=AIzaSy... 到 prod log + # 現用 _sanitize_error 過濾 ?key= query string + safe_error = _sanitize_error(e) + logger.warning("gemini_provider_failed", error=safe_error, latency_ms=round(latency, 1)) + return AIResult(raw_response="", success=False, provider=self.name, latency_ms=latency, error=safe_error) async def health_check(self) -> bool: return bool(settings.GEMINI_API_KEY)