scope Google TTS credentials safely

This commit is contained in:
Codex
2026-08-07 15:01:59 +00:00
parent 4eab34c5cf
commit 6b4c73c6df
2 changed files with 50 additions and 8 deletions

View File

@@ -0,0 +1,36 @@
from tools.audio.google_tts import GoogleTTS
from tools.google_credentials import has_google_credentials
def test_tts_only_key_does_not_enable_shared_google_providers(monkeypatch):
monkeypatch.setenv("GOOGLE_TTS_API_KEY", "test-tts-only-key")
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False)
assert GoogleTTS().get_status().value == "available"
assert has_google_credentials() is False
def test_tts_key_uses_header_and_is_redacted_from_errors(monkeypatch, tmp_path):
import requests
secret = "test-production-shaped-tts-key"
monkeypatch.setenv("GOOGLE_TTS_API_KEY", secret)
monkeypatch.delenv("GOOGLE_API_KEY", raising=False)
monkeypatch.delenv("GEMINI_API_KEY", raising=False)
monkeypatch.delenv("GOOGLE_APPLICATION_CREDENTIALS", raising=False)
def fail_request(url, **kwargs):
assert kwargs["headers"]["x-goog-api-key"] == secret
assert "params" not in kwargs
raise requests.HTTPError(f"403 for {url}?key={secret}")
monkeypatch.setattr(requests, "post", fail_request)
result = GoogleTTS().execute(
{"text": "safe test sentence", "output_path": str(tmp_path / "speech.mp3")}
)
assert result.success is False
assert secret not in result.error
assert "[REDACTED]" in result.error

View File

@@ -27,7 +27,6 @@ from tools.base_tool import (
from tools.google_credentials import (
get_access_token,
service_account_configured,
has_google_credentials,
)
@@ -44,7 +43,8 @@ class GoogleTTS(BaseTool):
dependencies = []
install_instructions = (
"Auth option A — API key: set GOOGLE_API_KEY (or GEMINI_API_KEY) to a\n"
"Auth option A — TTS-only API key: set GOOGLE_TTS_API_KEY.\n"
" GOOGLE_API_KEY or GEMINI_API_KEY remain supported for broader Google setups.\n"
" Google Cloud API key with Text-to-Speech enabled.\n"
" Enable the API at https://console.cloud.google.com/apis/library/texttospeech.googleapis.com\n"
"Auth option B — service account: set GOOGLE_APPLICATION_CREDENTIALS to the\n"
@@ -149,12 +149,16 @@ class GoogleTTS(BaseTool):
}
def _get_api_key(self) -> str | None:
return os.environ.get("GOOGLE_API_KEY") or os.environ.get("GEMINI_API_KEY")
return (
os.environ.get("GOOGLE_TTS_API_KEY")
or os.environ.get("GOOGLE_API_KEY")
or os.environ.get("GEMINI_API_KEY")
)
def get_status(self) -> ToolStatus:
# Available via either an API key or a service-account JSON. Both paths
# are honoured by execute() — so this no longer over-reports.
if has_google_credentials():
if self._get_api_key() or service_account_configured():
return ToolStatus.AVAILABLE
return ToolStatus.UNAVAILABLE
@@ -204,7 +208,11 @@ class GoogleTTS(BaseTool):
try:
result = self._generate(inputs, api_key=api_key, bearer_token=bearer_token)
except Exception as exc:
return ToolResult(success=False, error=f"Google TTS failed: {exc}")
safe_error = str(exc)
for credential in (api_key, bearer_token):
if credential:
safe_error = safe_error.replace(credential, "[REDACTED]")
return ToolResult(success=False, error=f"Google TTS failed: {safe_error}")
result.duration_seconds = round(time.time() - start, 2)
result.cost_usd = self.estimate_cost(inputs)
@@ -266,16 +274,14 @@ class GoogleTTS(BaseTool):
url = f"https://texttospeech.googleapis.com/{api_version}/text:synthesize"
headers = {"Content-Type": "application/json"}
params: dict[str, str] = {}
if bearer_token:
headers["Authorization"] = f"Bearer {bearer_token}"
elif api_key:
params["key"] = api_key
headers["x-goog-api-key"] = api_key
response = requests.post(
url,
headers=headers,
params=params,
json=payload,
timeout=120,
)