| Title | speaches-ai Speaches git master 2026-04-18 (commit 993994f) Unsafe Reflection |
|---|
| Description | A vulnerability was found in Speaches git commit 993994f7984bf3fe9655b267448328cf66fccb42 and classified as critical 9.8. Affected is the PyannoteDiarizationModelManager._load_fn function of the pyannote diarization model loading component (src/speaches/executors/pyannote_diarization.py), reached through the unauthenticated POST /v1/audio/diarization endpoint. The manipulation of the argument model leads to unsafe reflection (CWE-470): the model ID is selected by attacker-written model card metadata and passed to pyannote.audio Pipeline.from_pretrained(), which resolves the config.yaml pipeline.name field through get_class_by_name() without a module allowlist and invokes the resolved installed callable with the expanded params mapping. It is possible to launch the attack remotely. A model repository containing no program files (only README.md, config.yaml and a text marker) makes the server process invoke any installed callable with attacker-chosen arguments; the demonstrated payload huggingface_hub.snapshot_download with params.repo_id=victim/second-stage causes a server-side second-stage request for an attacker-chosen repository. pyannote.audio >= 4 injects token/cache_dir keyword arguments into the params, filtering strict-signature payloads such as subprocess.call (confirmed TypeError in reproduction), while kwargs-tolerant callables remain fully attacker-controlled and pyannote 3.x-era loaders do not inject these keywords.
Technical Details
- Affected file/function: src/speaches/executors/pyannote_diarization.py / PyannoteDiarizationModelManager._load_fn (lines 75-85), via src/speaches/routers/diarization.py (POST /v1/audio/diarization)
- Vulnerable parameter: model (model ID of the diarization request, routed solely by model card metadata)
- Attack vector: Network
- Privileges required: None (endpoint unauthenticated in the default configuration)
- Trigger condition: a repository whose metadata passes the diarization filter (pipeline_tag speaker-diarization, tag pyannote) is present in the local HuggingFace cache, then a single unauthenticated diarization request with that model ID resolves pipeline.name and invokes the selected callable
Impact
- Confidentiality: High (code execution in the server process exposes all data accessible to it)
- Integrity: High (attacker-chosen callable invocation permits arbitrary state changes and server-side requests)
- Availability: High (the invoked object can crash or monopolize the server process)
CVSS v3.1
Score: 9.8 (Critical)
Vector: CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H
Timeline
- Discovered: 2026-10-04
- Vendor notified: [pending]
- Patch released: [pending]
- Public disclosure: [pending]
Countermeasure
Enforce the AVAILABLE_MODELS allowlist (currently only applied to the listing/download endpoints) inside the load path before calling Pipeline.from_pretrained(), and stop relying on pyannote's config-driven reflection for untrusted repositories by instantiating the diarization pipeline class directly or validating config.yaml pipeline.name against a strict allowlist. |
|---|
| Source | ⚠️ https://github.com/speaches-ai/speaches/issues/679 |
|---|
| User | Dem0000000 (UID 98743) |
|---|
| Submission | 10/04/2026 17:58 (15 days ago) |
|---|
| Moderation | 10/19/2026 07:26 (15 days later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 422250 [speaches-ai speaches up to 0.9.0-rc.3 Pyannote Diarization Model Loading pyannote_diarization.py PyannoteDiarizationModelManager._load_fn model externally-controlled input to select classes or code] |
|---|
| Points | 20 |
|---|