Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions tests/tools/test_lazy_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,62 @@ def fake_satisfied(spec):
result = ld.refresh_active_features()
assert result["a.ok"] == "current"
assert result["b.fail"].startswith("failed:")


# ---------------------------------------------------------------------------
# mistralai_unlock_status — quarantine opt-in gate (#34503)
# ---------------------------------------------------------------------------

class TestMistralaiUnlockStatus:
"""The blanket mistralai ban is lifted only behind an explicit opt-in
env var AND a version floor (2.4.6 was malicious; >= 2.4.8 is clean)."""

@staticmethod
def _patch_version(monkeypatch, *, installed=None, missing=False):
import importlib.metadata as md

def fake_version(pkg):
if missing:
raise md.PackageNotFoundError(pkg)
return installed

monkeypatch.setattr(md, "version", fake_version)

def test_locked_by_default(self, monkeypatch):
monkeypatch.delenv(ld.MISTRALAI_UNLOCK_ENV, raising=False)
allowed, reason = ld.mistralai_unlock_status()
assert allowed is False
assert ld.MISTRALAI_UNLOCK_ENV in reason

def test_opt_in_but_not_installed(self, monkeypatch):
monkeypatch.setenv(ld.MISTRALAI_UNLOCK_ENV, "1")
self._patch_version(monkeypatch, missing=True)
allowed, reason = ld.mistralai_unlock_status()
assert allowed is False
assert "not installed" in reason

def test_opt_in_below_floor_refused(self, monkeypatch):
"""The known-malicious 2.4.6 is refused even with opt-in."""
monkeypatch.setenv(ld.MISTRALAI_UNLOCK_ENV, "1")
self._patch_version(monkeypatch, installed="2.4.6")
allowed, reason = ld.mistralai_unlock_status()
assert allowed is False
assert "2.4.6" in reason or "below the safe floor" in reason

def test_opt_in_clean_version_allowed(self, monkeypatch):
monkeypatch.setenv(ld.MISTRALAI_UNLOCK_ENV, "1")
self._patch_version(monkeypatch, installed="2.4.8")
allowed, reason = ld.mistralai_unlock_status()
assert allowed is True
assert "2.4.8" in reason

def test_opt_in_newer_version_allowed(self, monkeypatch):
monkeypatch.setenv(ld.MISTRALAI_UNLOCK_ENV, "1")
self._patch_version(monkeypatch, installed="2.5.0")
allowed, _ = ld.mistralai_unlock_status()
assert allowed is True

def test_falsey_env_value_stays_locked(self, monkeypatch):
monkeypatch.setenv(ld.MISTRALAI_UNLOCK_ENV, "0")
allowed, _ = ld.mistralai_unlock_status()
assert allowed is False
38 changes: 30 additions & 8 deletions tests/tools/test_transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -1012,29 +1012,51 @@ def test_permission_error(self, monkeypatch, sample_ogg, mock_mistral_module):
class TestGetProviderMistral:
"""Mistral-specific provider selection tests.

Mistral STT is intentionally disabled in 2026-05-12+ while the
`mistralai` PyPI package is quarantined. These tests document that
explicit `provider: mistral` always returns "none" with a warning, and
that auto-detect skips mistral entirely.
The `mistralai` PyPI package was quarantined on 2026-05-12 (malicious
2.4.6 release). The blanket ban is lifted only behind an explicit
opt-in (HERMES_ALLOW_MISTRALAI) plus a version floor enforced by
tools.lazy_deps.mistralai_unlock_status(). Without the opt-in, explicit
`provider: mistral` still returns "none"; auto-detect always skips it.
"""

def test_mistral_when_key_and_sdk_available(self, monkeypatch):
"""Even with key + SDK, explicit mistral returns 'none' (disabled)."""
def test_mistral_locked_by_default_returns_none(self, monkeypatch):
"""Without the opt-in env var, explicit mistral returns 'none'."""
monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.delenv("HERMES_ALLOW_MISTRALAI", raising=False)
with patch("tools.transcription_tools._HAS_MISTRAL", True):
from tools.transcription_tools import _get_provider
assert _get_provider({"provider": "mistral"}) == "none"

def test_mistral_unlocked_with_clean_version_and_key(self, monkeypatch):
"""Opt-in + clean SDK version + key returns 'mistral'."""
monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.setenv("HERMES_ALLOW_MISTRALAI", "1")
with patch("tools.lazy_deps.mistralai_unlock_status",
return_value=(True, "unlocked")):
from tools.transcription_tools import _get_provider
assert _get_provider({"provider": "mistral"}) == "mistral"

def test_mistral_unlocked_but_no_key_returns_none(self, monkeypatch):
"""Opt-in + clean SDK but missing key returns 'none'."""
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
monkeypatch.setenv("HERMES_ALLOW_MISTRALAI", "1")
with patch("tools.lazy_deps.mistralai_unlock_status",
return_value=(True, "unlocked")):
from tools.transcription_tools import _get_provider
assert _get_provider({"provider": "mistral"}) == "none"

def test_mistral_explicit_no_key_returns_none(self, monkeypatch):
"""Explicit mistral with no key returns none."""
"""Explicit mistral with no key returns none (locked)."""
monkeypatch.delenv("MISTRAL_API_KEY", raising=False)
monkeypatch.delenv("HERMES_ALLOW_MISTRALAI", raising=False)
with patch("tools.transcription_tools._HAS_MISTRAL", True):
from tools.transcription_tools import _get_provider
assert _get_provider({"provider": "mistral"}) == "none"

def test_mistral_explicit_no_sdk_returns_none(self, monkeypatch):
"""Explicit mistral with key but no SDK returns none."""
"""Explicit mistral with key but no SDK returns none (locked)."""
monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.delenv("HERMES_ALLOW_MISTRALAI", raising=False)
with patch("tools.transcription_tools._HAS_MISTRAL", False):
from tools.transcription_tools import _get_provider
assert _get_provider({"provider": "mistral"}) == "none"
Expand Down
45 changes: 35 additions & 10 deletions tests/tools/test_tts_mistral.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,39 +162,64 @@ def test_model_from_config_overrides_default(


class TestTtsDispatcherMistral:
def test_dispatcher_returns_disabled_error(
def test_dispatcher_returns_disabled_error_when_locked(
self, tmp_path, mock_mistral_module, monkeypatch
):
"""Mistral TTS is intentionally disabled (PyPI quarantine 2026-05-12).

The dispatcher must short-circuit with a clear status message before
attempting any SDK import, even when MISTRAL_API_KEY is set and a
mock SDK is wired in. Restore routing once `mistralai` is
un-quarantined on PyPI.
"""Without the HERMES_ALLOW_MISTRALAI opt-in, the dispatcher must
short-circuit with a clear status message before attempting any SDK
import — even when MISTRAL_API_KEY is set and a mock SDK is wired in.
"""
import json

from tools.tts_tool import text_to_speech_tool

monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.delenv("HERMES_ALLOW_MISTRALAI", raising=False)

output_path = str(tmp_path / "out.mp3")
with patch("tools.tts_tool._load_tts_config", return_value={"provider": "mistral"}):
result = json.loads(text_to_speech_tool("Hello", output_path=output_path))

assert result["success"] is False
assert "temporarily disabled" in result["error"]
assert "disabled" in result["error"]
assert "quarantined" in result["error"]
# SDK must not have been called.
mock_mistral_module.audio.speech.complete.assert_not_called()

def test_dispatcher_routes_to_generator_when_unlocked(
self, tmp_path, mock_mistral_module, monkeypatch
):
"""With the opt-in + a clean SDK, the dispatcher routes to the
Mistral generator instead of short-circuiting.
"""
import base64
import json

from tools.tts_tool import text_to_speech_tool

monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.setenv("HERMES_ALLOW_MISTRALAI", "1")
mock_mistral_module.audio.speech.complete.return_value = MagicMock(
audio_data=base64.b64encode(b"audio").decode()
)

output_path = str(tmp_path / "out.mp3")
with patch("tools.lazy_deps.mistralai_unlock_status",
return_value=(True, "unlocked")), \
patch("tools.tts_tool._load_tts_config", return_value={"provider": "mistral"}):
result = json.loads(text_to_speech_tool("Hello", output_path=output_path))

assert result["success"] is True
mock_mistral_module.audio.speech.complete.assert_called_once()

def test_dispatcher_returns_error_when_sdk_not_installed(self, tmp_path, monkeypatch):
"""Same disabled message regardless of SDK presence."""
"""Locked-by-default message regardless of SDK presence."""
import json

from tools.tts_tool import text_to_speech_tool

monkeypatch.setenv("MISTRAL_API_KEY", "test-key")
monkeypatch.delenv("HERMES_ALLOW_MISTRALAI", raising=False)
with patch(
"tools.tts_tool._import_mistral_client", side_effect=ImportError("no module")
), patch("tools.tts_tool._load_tts_config", return_value={"provider": "mistral"}):
Expand All @@ -203,7 +228,7 @@ def test_dispatcher_returns_error_when_sdk_not_installed(self, tmp_path, monkeyp
)

assert result["success"] is False
assert "temporarily disabled" in result["error"]
assert "disabled" in result["error"]


class TestCheckTtsRequirementsMistral:
Expand Down
96 changes: 96 additions & 0 deletions tools/lazy_deps.py
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,102 @@ def is_available(feature: str) -> bool:
return not feature_missing(feature)


# ---------------------------------------------------------------------------
# mistralai quarantine unlock
# ---------------------------------------------------------------------------
#
# The ``mistralai`` PyPI project was quarantined on 2026-05-12 after a
# malicious **2.4.6** release (the "Mini Shai-Hulud" supply-chain worm).
# The quarantine was version-specific: 2.4.8 and later are clean. Because
# the malicious version is still installable from caches/mirrors, Hermes
# blanket-banned the ``mistral`` STT/TTS providers at runtime.
#
# That blanket ban left users who have *manually verified* a clean install
# (>=2.4.8) with no escape hatch. This helper lifts the ban behind an
# explicit opt-in env var (mirroring ``HERMES_ALLOW_PRIVATE_URLS`` in
# ``tools/url_safety.py``) AND a hard version floor, so the known-malicious
# 2.4.6 can never be re-enabled even if the user opts in.

#: Lowest ``mistralai`` version considered safe (2.4.6 was the malicious
#: release; 2.4.8 is the first clean post-quarantine release per #34503).
MISTRALAI_MIN_SAFE_VERSION = "2.4.8"

#: Opt-in env var. Set to a truthy value (``1``/``true``/``yes``/``on``) to
#: allow the ``mistral`` STT/TTS providers once a clean SDK is installed.
MISTRALAI_UNLOCK_ENV = "HERMES_ALLOW_MISTRALAI"


def _env_truthy(name: str) -> bool:
return os.getenv(name, "").strip().lower() in ("1", "true", "yes", "on")


def mistralai_unlock_status() -> tuple[bool, str]:
"""Decide whether the quarantined ``mistralai`` SDK may be used.

Returns a ``(allowed, reason)`` tuple:

* ``allowed`` — True only when the user has explicitly opted in via
:data:`MISTRALAI_UNLOCK_ENV` AND an installed ``mistralai`` is at or
above :data:`MISTRALAI_MIN_SAFE_VERSION`.
* ``reason`` — a short, user-facing explanation suitable for logging or
surfacing in an error message.

The version floor is enforced even when the user opts in, so the
known-malicious 2.4.6 can never be re-enabled by this path.
"""
if not _env_truthy(MISTRALAI_UNLOCK_ENV):
return (
False,
"`mistralai` is quarantined (malicious 2.4.6 release, 2026-05-12). "
f"Set {MISTRALAI_UNLOCK_ENV}=1 to re-enable it after confirming you "
f"have a clean install (>= {MISTRALAI_MIN_SAFE_VERSION}).",
)

try:
from importlib.metadata import PackageNotFoundError, version
except ImportError: # pragma: no cover - importlib.metadata always present
return (False, "Cannot determine installed `mistralai` version.")

try:
installed = version("mistralai")
except PackageNotFoundError:
return (
False,
f"{MISTRALAI_UNLOCK_ENV} is set but the `mistralai` package is not "
"installed.",
)
except Exception:
return (False, "Cannot determine installed `mistralai` version.")

try:
from packaging.version import InvalidVersion, Version
except ImportError:
# packaging unavailable — refuse rather than guess, since the whole
# point of this gate is the version floor.
return (
False,
"`packaging` unavailable; cannot verify the installed `mistralai` "
"version meets the safety floor.",
)

try:
if Version(installed) < Version(MISTRALAI_MIN_SAFE_VERSION):
return (
False,
f"Installed `mistralai` {installed} is below the safe floor "
f"{MISTRALAI_MIN_SAFE_VERSION} (2.4.6 was malicious). Upgrade "
"to a clean release.",
)
except (InvalidVersion, Exception):
return (
False,
f"Installed `mistralai` version {installed!r} is unparseable; "
"refusing to enable.",
)

return (True, f"`mistralai` {installed} unlocked via {MISTRALAI_UNLOCK_ENV}.")


def feature_install_command(feature: str) -> Optional[str]:
"""Return the ``pip install`` command a user could run manually, or None."""
if feature not in LAZY_DEPS:
Expand Down
31 changes: 20 additions & 11 deletions tools/transcription_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -793,17 +793,26 @@ def _get_provider(stt_config: dict) -> str:

if provider == "mistral":
# `mistralai` PyPI package was quarantined on 2026-05-12 after a
# malicious 2.4.6 release. Refuse to use this provider until it's
# available again so we surface a clear message instead of an
# opaque ImportError mid-call.
logger.warning(
"STT provider 'mistral' (Voxtral Transcribe) is temporarily "
"disabled — `mistralai` PyPI package is quarantined "
"(malicious 2.4.6 release on 2026-05-12). Falling back to "
"another provider. Set stt.provider in config.yaml to 'local' "
"or 'openai' to silence this warning."
)
return "none"
# malicious 2.4.6 release. The ban is lifted only behind an
# explicit opt-in (HERMES_ALLOW_MISTRALAI) + a version floor; see
# tools/lazy_deps.mistralai_unlock_status().
from tools.lazy_deps import mistralai_unlock_status

allowed, reason = mistralai_unlock_status()
if not allowed:
logger.warning(
"STT provider 'mistral' (Voxtral Transcribe) unavailable: "
"%s Falling back to another provider.",
reason,
)
return "none"
if not get_env_value("MISTRAL_API_KEY"):
logger.warning(
"STT provider 'mistral' configured but MISTRAL_API_KEY "
"is not set. Falling back to another provider."
)
return "none"
return "mistral"

if provider == "xai":
from tools.xai_http import resolve_xai_http_credentials
Expand Down
32 changes: 18 additions & 14 deletions tools/tts_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -1975,20 +1975,24 @@ def text_to_speech_tool(

elif provider == "mistral":
# `mistralai` PyPI package was quarantined on 2026-05-12 after a
# malicious 2.4.6 release. Surface a clear status message instead
# of attempting an import that would either fail or pull a stale
# cached package.
return json.dumps({
"success": False,
"error": (
"Mistral Voxtral TTS is temporarily disabled. The "
"`mistralai` PyPI package was quarantined on 2026-05-12 "
"after a malicious 2.4.6 release. Switch tts.provider in "
"config.yaml to 'edge', 'elevenlabs', 'openai', 'minimax', "
"'gemini', 'xai', 'neutts', or 'kittentts'. Mistral "
"support will return once PyPI un-quarantines the package."
),
}, ensure_ascii=False)
# malicious 2.4.6 release. The ban is lifted only behind an
# explicit opt-in (HERMES_ALLOW_MISTRALAI) + a version floor; see
# tools/lazy_deps.mistralai_unlock_status().
from tools.lazy_deps import mistralai_unlock_status

allowed, reason = mistralai_unlock_status()
if not allowed:
return json.dumps({
"success": False,
"error": (
"Mistral Voxtral TTS is disabled. " + reason + " "
"Alternatively switch tts.provider in config.yaml to "
"'edge', 'elevenlabs', 'openai', 'minimax', 'gemini', "
"'xai', 'neutts', or 'kittentts'."
),
}, ensure_ascii=False)
logger.info("Generating speech with Mistral Voxtral TTS...")
_generate_mistral_tts(text, file_str, tts_config)

elif provider == "gemini":
logger.info("Generating speech with Google Gemini TTS...")
Expand Down
Loading