From 3ae25e0fbd6e191b72c45d1581603227dc6c6ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B3=AF=E5=B2=B8=E3=80=80=E4=BA=AE?= <1920071390@campus.ouj.ac.jp> Date: Fri, 24 Jul 2026 07:49:29 +0900 Subject: [PATCH] fix(security): scrub credentials from voice playback subprocesses ## Summary - Spawn system audio players (`ffplay` / `afplay` / `aplay`) with `hermes_subprocess_env(inherit_credentials=False)`. - Prevent gateway tokens and provider API keys from leaking into OS media helpers. - Add a regression test asserting scrubbed env on `Popen`. ## Salvage / credit Sibling of #70342 / incomplete #56332 (TTS/STT command scrub) on the voice-mode playback path. --- .../test_voice_mode_playback_env_scrub.py | 32 +++++++++++++++++++ tools/voice_mode.py | 12 ++++++- 2 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 tests/tools/test_voice_mode_playback_env_scrub.py diff --git a/tests/tools/test_voice_mode_playback_env_scrub.py b/tests/tools/test_voice_mode_playback_env_scrub.py new file mode 100644 index 0000000000000..75cc385845526 --- /dev/null +++ b/tests/tools/test_voice_mode_playback_env_scrub.py @@ -0,0 +1,32 @@ +"""Voice-mode system playback must scrub credential env (sibling of #70342).""" + +from unittest.mock import MagicMock, patch + + +def test_play_audio_file_scrubbed_env(tmp_path, monkeypatch): + audio = tmp_path / "t.mp3" + audio.write_bytes(b"ID3fake") + + monkeypatch.setenv("TELEGRAM_BOT_TOKEN", "secret-token") + monkeypatch.setenv("OPENAI_API_KEY", "sk-test") + + captured = {} + + def fake_popen(cmd, **kwargs): + captured["env"] = kwargs.get("env") + proc = MagicMock() + proc.wait.return_value = 0 + return proc + + import tools.voice_mode as vm + + with patch.object(vm, "platform") as plat, patch.object( + vm.shutil, "which", return_value="/usr/bin/ffplay" + ), patch.object(vm.subprocess, "Popen", side_effect=fake_popen): + plat.system.return_value = "Linux" + ok = vm.play_audio_file(str(audio)) + + assert ok is True + assert captured.get("env") is not None + assert "TELEGRAM_BOT_TOKEN" not in captured["env"] + assert "OPENAI_API_KEY" not in captured["env"] diff --git a/tools/voice_mode.py b/tools/voice_mode.py index 9f9636234cf17..68eb59e7c96ac 100644 --- a/tools/voice_mode.py +++ b/tools/voice_mode.py @@ -1510,7 +1510,17 @@ def play_audio_file(file_path: str) -> bool: exe = shutil.which(cmd[0]) if exe: try: - proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, stdin=subprocess.DEVNULL) + # Sibling of TTS/STT credential scrub (#70342 / #56332): system + # audio players must not inherit gateway tokens / API keys. + from tools.environments.local import hermes_subprocess_env + + proc = subprocess.Popen( + cmd, + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + stdin=subprocess.DEVNULL, + env=hermes_subprocess_env(inherit_credentials=False), + ) with _playback_lock: _active_playback = proc proc.wait(timeout=300)