Skip to content

fix(security): scrub credentials from ffmpeg/ffprobe media helpers - #73879

Open
Drexuxux wants to merge 1 commit into
NousResearch:mainfrom
Drexuxux:fix/media-helper-credential-scrub
Open

fix(security): scrub credentials from ffmpeg/ffprobe media helpers#73879
Drexuxux wants to merge 1 commit into
NousResearch:mainfrom
Drexuxux:fix/media-helper-credential-scrub

Conversation

@Drexuxux

@Drexuxux Drexuxux commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

What

The TTS/STT command scrub (#56332 / #70342) and the voice-mode playback scrub established the rule that an OS media helper must not inherit Hermes credentials. ffmpeg and ffprobe are the same kind of process — third-party binaries Hermes shells out to for transcoding and duration probes — and three of them still ran with the full process environment:

site when it runs
tools/tts_tool.py::_ffmpeg_transcode_to_opus every voice-note TTS reply (OGG transcode)
plugins/platforms/telegram/adapter.py::_probe_voice_duration_seconds every inbound voice note
plugins/platforms/discord/adapter.py::_probe_audio_duration_seconds every inbound audio attachment

Each one saw every provider API key, bot token and SUDO_PASSWORD present in the process env. The first is in the same file whose command provider was already scrubbed — a sibling path of the very fix that introduced the rule.

Fix

Pass hermes_subprocess_env(inherit_credentials=False), matching the sibling call sites (voice playback, TTS/STT command provider, transcription).

Deliberately left alone: the NeuTTS synthesis subprocess in the same file — it runs Hermes's own tools/neutts_synth.py, not a third-party binary.

Tests

tests/tools/test_media_helper_env_scrub.py — seeds OPENAI_API_KEY / ANTHROPIC_API_KEY / TELEGRAM_BOT_TOKEN, captures the spawn kwargs and asserts none of them reach the child. Mirrors the existing test_voice_mode_playback_env_scrub.py.

  • All three fail on main (env is None — the helper inherited everything) and pass with this change.
  • pytest tests/tools/ -q -k "tts or voice or transcription" → 927 passed / 17 pre-existing failures; the stashed baseline shows the same 17 (18 there only because the new tts test is counted before the fix).
  • pytest tests/gateway/ -q -k "telegram or discord" → 2431 passed / 14 failed, byte-identical to the stashed baseline.

The TTS/STT command scrub (NousResearch#56332 / NousResearch#70342) and the voice-mode playback
scrub established the rule: an OS media helper must not inherit Hermes
credentials. ffmpeg and ffprobe are the same kind of process — third-party
binaries shelled out to for transcoding and duration probes — and three of
them still ran with the full process environment:

- tools/tts_tool.py::_ffmpeg_transcode_to_opus — the voice-note OGG
  transcode, in the same file whose command provider was already scrubbed
- plugins/platforms/telegram/adapter.py::_probe_voice_duration_seconds
- plugins/platforms/discord/adapter.py::_probe_audio_duration_seconds

Each one therefore saw every provider API key, bot token and SUDO_PASSWORD
in the process env, on paths that run for ordinary inbound voice notes and
outbound TTS replies.

Pass hermes_subprocess_env(inherit_credentials=False), matching the sibling
call sites. The NeuTTS synthesis subprocess is deliberately left alone: it
runs Hermes's own tools/neutts_synth.py, not a third-party binary.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening comp/plugins Plugin system and bundled plugins tool/tts Text-to-speech and transcription platform/discord Discord bot adapter platform/telegram Telegram bot adapter P3 Low — cosmetic, nice to have labels Jul 29, 2026
@teknium1

Copy link
Copy Markdown
Contributor

Thanks for tracing the ffmpeg/ffprobe paths and using the existing scrubber. The three changed call sites match the established mechanism in tools/environments/local.py:561 and fix real current-main omissions at tools/tts_tool.py:1272, plugins/platforms/telegram/adapter.py:365, and plugins/platforms/discord/adapter.py:3828.

Problems

  • The same media-helper class remains exposed in unmodified TTS conversions: tools/tts_tool.py:2401, tools/tts_tool.py:2496, tools/tts_tool.py:2703, and tools/tts_tool.py:2769 all invoke ffmpeg without env. In particular, leaving the NeuTTS Python synthesis child alone does not cover its separate ffmpeg conversion at tools/tts_tool.py:2496.
  • Other current first-party media helpers likewise inherit the process environment: gateway/run.py:2636, gateway/platforms/whatsapp_cloud.py:1266, gateway/platforms/qqbot/adapter.py:2122, and gateway/platforms/signal.py:167.

Suggested changes

  • Extend the same scrubbed environment to the remaining ffmpeg/ffprobe spawns and add coverage for the async subprocess variants.

Automated hermes-sweeper review.

@teknium1 teknium1 added sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform labels Jul 30, 2026
@egilewski

Copy link
Copy Markdown
Contributor

suggesting changes

The three edited subprocesses now use a credential-scrubbed environment and the new tests pass, but the claimed media-helper invariant is incomplete. Multiple ffmpeg/ffprobe subprocesses in Discord, gateway, TTS, STT, Matrix, Signal, QQ, WhatsApp, and other plugins still inherit os.environ, so the residual credential exposure requires changes.

  • [P2] Other media-helper subprocesses still inherit Hermes secrets (plugins/platforms/discord/adapter.py:927)
    The patch covers only tools/tts_tool.py::_ffmpeg_transcode_to_opus and the Telegram and Discord duration probes. Other OS media helpers still omit env and therefore inherit the Hermes process environment, including Discord pcm_to_wav at line 927, gateway/run.py ffprobe at line 2902, TTS chunk combination and built-in conversions at lines 1601, 2756, 2851, 3058, and 3124, STT encoding/preparation/probing at lines 256, 2012, 2030, and 2793, and equivalent Matrix, Signal, QQ, WhatsApp, Teams, Google Meet, and Discord voice-mixer paths. A helper found on PATH or otherwise compromised can read provider API keys, gateway tokens, SUDO_PASSWORD, and dynamic Hermes credentials. The new tests exercise only the three edited call sites, so they do not establish the invariant for these reachable sinks.
    Remediation: Route every ffmpeg, ffprobe, afconvert, and equivalent OS media-helper spawn through hermes_subprocess_env(inherit_credentials=False), preserving only explicitly required non-Hermes variables. Add environment-capture tests for each caller family, including dynamic and internal secret names, and cover both synchronous and asyncio subprocess APIs.

Security evidence:

  • trust boundary: Hermes holds provider credentials, gateway tokens, and internal secrets in its process environment. ffmpeg and ffprobe are external local binaries invoked for media conversion or probing and are outside that trust boundary. The three changed calls now construct a sanitized child environment, while omitted env arguments at other media-helper sinks still expose the parent environment.
  • source/sink/invariant: Credential sources include os.environ, provider configuration, and gateway configuration. subprocess.run and asyncio.create_subprocess_exec inherit os.environ when env is omitted. The changed calls pass hermes_subprocess_env(inherit_credentials=False), which removes gateway and provider secrets; the residual call sites still omit env and violate the same invariant.
  • current-main reproduction: The same missing environment arguments are present on current GitHub main outside the three changed call sites, confirming the issue is independent of the new tests.
  • PR-head or patch-replay validation: The reviewed patch replay against current GitHub main includes the PR changes; the three added tests pass and source inspection confirms the environment is supplied at the edited call sites.
  • positive/negative cases: Environment-capture tests for TTS ffmpeg and Telegram and Discord ffprobe confirm seeded credentials are absent while benign variables remain available.
  • residual bypass search: Review of media-helper call sites found direct no-env subprocess calls in Discord PCM conversion and voice mixing, gateway duration probing, TTS combination and provider conversions, STT conversion and duration probing, Matrix media metadata/transcoding, Signal remuxing, QQ and WhatsApp media conversion, Teams extraction, and Google Meet realtime helpers.
  • reviewer validation: The changed tests pass, and source review confirms the residual sinks described above; the conclusion is based on those reachable calls.

Review setup: I reviewed a run-owned local rebase or patch replay against current GitHub main because the submitted branch is stale or conflicted; this does not mean the submitted branch itself merges cleanly.

Not checked:

  • Ruff validation
  • Real hostile media-helper execution
  • Broader media-helper regression suite

Signed: GPT-5.6-luna-max in Codex

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/plugins Plugin system and bundled plugins P3 Low — cosmetic, nice to have platform/discord Discord bot adapter platform/telegram Telegram bot adapter sweeper:blast-moderate Sweeper blast radius: moderate — a subsystem or single platform sweeper:risk-compatibility Sweeper risk: may break existing users, config, migrations, defaults, or upgrades sweeper:risk-security-boundary Sweeper risk: may affect sandboxing, auth, credentials, or sensitive data tool/tts Text-to-speech and transcription type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants