fix(stt): route transcription inputs through the shared read guard

`transcribe_audio` reads a local file and hands it to the configured STT
provider — for the hosted providers (Groq, OpenAI, Mistral, xAI, ElevenLabs)
that ships the file's bytes to a third-party API. The same local-input read
guard was added to image-gen (587be5b5b) and xAI video-gen (104232979) to keep
the agent from feeding credential/secret stores to a provider, but STT was
missed.

Call `get_read_block_error(file_path)` at the top of `transcribe_audio`, before
validation/dispatch, so a `.env`, `auth.json`, `.anthropic_oauth.json`,
`mcp-tokens/`, etc. is refused up front instead of being transcribed (and, for
hosted providers, exfiltrated). This is defense-in-depth, not a security
boundary — the guard's own message says so — but it restores parity with the
image/video-gen tools.

Regression test: a `.env` file is refused with the shared read-guard message
before any provider dispatch (mutation-verified).
This commit is contained in:
Frowtek 2026-07-04 06:21:25 +03:00 committed by Teknium
parent 37d0b6c81a
commit 38bb193f38
2 changed files with 36 additions and 1 deletions

View file

@ -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

View file

@ -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.