diff --git a/tests/tools/test_transcription_tools.py b/tests/tools/test_transcription_tools.py index 581b6ccbec50..6fdf9dd6cff4 100644 --- a/tests/tools/test_transcription_tools.py +++ b/tests/tools/test_transcription_tools.py @@ -2317,7 +2317,9 @@ class TestLocalBaseUrlNoApiKey: assert not _is_local_or_private_url("") -# ============================================================================ +# ===================================================================== + + # CAF (iMessage voice note) conversion tests # ============================================================================ @@ -2456,3 +2458,27 @@ class TestCafConversion: assert result["success"] is True mock_convert.assert_not_called() +======= +class TestTranscribeCredentialReadGuard: + """transcribe_audio must refuse credential/secret stores before dispatch.""" + + def test_transcribe_audio_blocks_credential_read(self, tmp_path): + """A ``.env`` (secret-bearing) file is refused up front, so its + plaintext is never shipped to an external STT provider — mirroring the + read guard added to image-gen (587be5b5b) and xAI video-gen + (104232979).""" + from tools.transcription_tools import transcribe_audio + from agent.file_safety import get_read_block_error + + env_file = tmp_path / ".env" + env_file.write_text("OPENAI_API_KEY=sk-secret\n") + + expected = get_read_block_error(str(env_file)) + assert expected, "test setup: a .env file should be read-blocked" + + result = transcribe_audio(str(env_file)) + + assert result["success"] is False + # The error is the shared read-guard message, not an audio-validation + # or provider error — proving the guard fired before dispatch. + assert result["error"] == expected diff --git a/tools/transcription_tools.py b/tools/transcription_tools.py index 4f228f470117..877f799621c0 100644 --- a/tools/transcription_tools.py +++ b/tools/transcription_tools.py @@ -2144,6 +2144,15 @@ def _transcribe_prepared_audio(file_path: str, model: Optional[str] = None) -> D - "error" (str, optional): Error message if success is False - "provider" (str, optional): Which provider was used """ + # Refuse to feed a credential / secret store (auth.json, .env, OAuth + # tokens, mcp-tokens/, ...) to an STT provider: an external provider would + # ship its plaintext contents to a third-party API. Mirrors the local-input + # read guard added to image-gen (587be5b5b) and xAI video-gen (104232979). + from agent.file_safety import get_read_block_error + blocked = get_read_block_error(file_path) + if blocked: + return {"success": False, "transcript": "", "error": blocked} + # Apply common path validation before provider resolution so invalid files # cannot trigger provider setup or lazy installation. The remote-upload # size cap is enforced separately below, only for non-local providers.