fix(security): keep Gemini key out of request URLs
All checks were successful
CD Pipeline / build-and-deploy (push) Successful in 15m5s

This commit is contained in:
Your Name
2026-04-29 22:56:12 +08:00
parent b857be0a64
commit d845d53257
8 changed files with 72 additions and 9 deletions

View File

@@ -10,7 +10,7 @@ Google Gemini Cloud API (gemini-2.0-flash)
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
修法:改用 x-goog-api-key header並保留 _sanitize_error 當防線
"""
from __future__ import annotations
@@ -22,7 +22,7 @@ import httpx
import structlog
from src.core.config import get_settings
from src.services.ai_providers.interfaces import AIProvider, AIResult, is_provider_enabled_by_env
from src.services.ai_providers.interfaces import AIResult, is_provider_enabled_by_env
from src.services.model_registry import get_model_registry
logger = structlog.get_logger(__name__)
@@ -91,7 +91,8 @@ class GeminiProvider:
model_name = registry.get_model("gemini", "rca")
response = await client.post(
f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={settings.GEMINI_API_KEY}",
f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent",
headers={"x-goog-api-key": settings.GEMINI_API_KEY},
json={
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {

View File

@@ -715,7 +715,7 @@ async def _call_gemini(prompt: str, api_key: str) -> str | None:
async with httpx.AsyncClient(timeout=30) as client:
resp = await client.post(
url,
params={"key": api_key},
headers={"x-goog-api-key": api_key},
json={"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {"temperature": 0.1}},
)

View File

@@ -119,7 +119,8 @@ class ChatManager:
try:
async with httpx.AsyncClient(timeout=30.0) as client:
resp = await client.post(
f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent?key={api_key}",
f"https://generativelanguage.googleapis.com/v1beta/models/{model}:generateContent",
headers={"x-goog-api-key": api_key},
json={
"system_instruction": {"parts": [{"text": system_prompt}]},
"contents": [{"parts": [{"text": user_message}]}],

View File

@@ -244,7 +244,7 @@ class HeartbeatReportService:
model_status[required] = ok
return {
"probe": ProbeResult(True, f"✅ 正常", round(latency, 1)),
"probe": ProbeResult(True, "✅ 正常", round(latency, 1)),
"models": model_status,
}
except Exception as e:
@@ -279,7 +279,8 @@ class HeartbeatReportService:
async with httpx.AsyncClient(timeout=_PROBE_TIMEOUT) as client:
t0 = asyncio.get_event_loop().time()
resp = await client.get(
f"https://generativelanguage.googleapis.com/v1beta/models?key={settings.GEMINI_API_KEY}",
"https://generativelanguage.googleapis.com/v1beta/models",
headers={"x-goog-api-key": settings.GEMINI_API_KEY},
)
latency = (asyncio.get_event_loop().time() - t0) * 1000
if resp.status_code == 200:

View File

@@ -105,7 +105,8 @@ async def probe_gemini_version() -> ProviderVersionInfo:
async with httpx.AsyncClient(timeout=8.0) as client:
resp = await client.get(
"https://generativelanguage.googleapis.com/v1beta/models",
params={"key": api_key, "pageSize": 50},
params={"pageSize": 50},
headers={"x-goog-api-key": api_key},
)
resp.raise_for_status()
data = resp.json()

View File

@@ -507,7 +507,8 @@ class OpenClawService:
model_name = registry.get_model("gemini", "rca")
response = await client.post(
f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent?key={settings.GEMINI_API_KEY}",
f"https://generativelanguage.googleapis.com/v1beta/models/{model_name}:generateContent",
headers={"x-goog-api-key": settings.GEMINI_API_KEY},
json={
"contents": [{"parts": [{"text": prompt}]}],
"generationConfig": {