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.
This commit is contained in:
峯岸 亮 2026-07-24 07:49:29 +09:00 committed by Teknium
parent 24a6fb6448
commit 3ae25e0fbd
2 changed files with 43 additions and 1 deletions

View File

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

View File

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