From 37d0b6c81af01a78a4dee3ebceb3b4c513cb0576 Mon Sep 17 00:00:00 2001 From: dsad Date: Sat, 4 Jul 2026 00:58:30 +0300 Subject: [PATCH] fix(tts): block output to protected paths --- tests/tools/test_tts_path_traversal.py | 41 ++++++++++++++++++++++++++ tools/tts_tool.py | 10 +++++++ 2 files changed, 51 insertions(+) diff --git a/tests/tools/test_tts_path_traversal.py b/tests/tools/test_tts_path_traversal.py index e6b20d817c05..b38071fd8f0d 100644 --- a/tests/tools/test_tts_path_traversal.py +++ b/tests/tools/test_tts_path_traversal.py @@ -58,3 +58,44 @@ def test_output_path_relative_no_dotdot_passes_guard(tmp_path, monkeypatch): )) error = result.get("error", "") assert "traversal" not in error.lower() + + +def test_output_path_rejects_hermes_oauth_store(tmp_path, monkeypatch): + """TTS output_path must not bypass the shared protected-file write guard.""" + import agent.file_safety as file_safety + + hermes_home = tmp_path / "hermes-home" + hermes_home.mkdir() + monkeypatch.setattr(file_safety, "_hermes_home_path", lambda: hermes_home) + monkeypatch.setattr(file_safety, "_hermes_root_path", lambda: hermes_home) + + target = hermes_home / ".anthropic_oauth.json" + result = json.loads(text_to_speech_tool( + text="hello", + output_path=str(target), + )) + + assert result["success"] is False + assert "protected credential" in result["error"] + assert not target.exists() + + +def test_output_path_rejects_mcp_token_directory(tmp_path, monkeypatch): + """TTS output_path must not write synthesized audio over MCP token files.""" + import agent.file_safety as file_safety + + hermes_home = tmp_path / "hermes-home" + token_dir = hermes_home / "mcp-tokens" + token_dir.mkdir(parents=True) + monkeypatch.setattr(file_safety, "_hermes_home_path", lambda: hermes_home) + monkeypatch.setattr(file_safety, "_hermes_root_path", lambda: hermes_home) + + target = token_dir / "server.mp3" + result = json.loads(text_to_speech_tool( + text="hello", + output_path=str(target), + )) + + assert result["success"] is False + assert "protected credential" in result["error"] + assert not target.exists() diff --git a/tools/tts_tool.py b/tools/tts_tool.py index 2b9b33109a7c..0d59c3100569 100644 --- a/tools/tts_tool.py +++ b/tools/tts_tool.py @@ -2868,6 +2868,16 @@ def text_to_speech_tool( file_path = _configured_command_tts_output_path( file_path, command_provider_config ) + from agent.file_safety import is_write_denied + + if is_write_denied(str(file_path)): + return json.dumps({ + "success": False, + "error": ( + f"output_path targets a protected credential or system path: " + f"{file_path}. Choose a normal audio output location." + ), + }, ensure_ascii=False) else: timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S_%f") out_dir = Path(DEFAULT_OUTPUT_DIR)