Back to skill

Security audit

Qwen Audio

Security checks for vulnerabilities and agentic risk

Overview

This audio skill mostly matches its TTS/STT purpose, but it has review-worthy risks around automatic package installation, default third-party model retrieval, and unsafe voice-profile path handling.

Install only if you are comfortable with an audio tool that can download models, use a third-party model mirror by default, modify its Python environment on macOS, and persist voice samples and transcripts. Review or patch the voice ID path handling before using custom voice IDs, avoid cloning voices without clear permission, and prefer pinned dependencies and an explicit official model endpoint.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/qwen-audio.py:747
Finding
Unsanitized Voice IDs Permit Filesystem Path Traversal<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qwen-audio.py:747-766` and `scripts/qwen-audio.py:778-834` **Vulnerability Type**: Path traversal and arbitrary filesystem access **Risk Level**: High ### Vulnerable Code ```python def get_voice_path(voice_id: str) -> dict | None: """Get voice file paths by voice_id. Returns dict with ref_audio, ref_text and instruct, or None if not found.""" voices_dir = get_voices_dir() voice_dir = os.path.join(voices_dir, voice_id) if not os.path.isdir(voice_dir): return None ref_audio = os.path.join(voice_dir, "ref_audio.wav") ref_text_path = os.path.join(voice_dir, "ref_text.txt") instruct_path = os.path.join(voice_dir, "ref_instruct.txt") if not os.path.exists(ref_audio): return None ref_text = "" if os.path.exists(ref_text_path): with open(ref_text_path, "r", encoding="utf-8") as f: ref_text = f.read().strip() ``` ```python def run_voice_create(args: argparse.Namespace) -> None: """Create a new voice by generating audio and saving reference info.""" voices_dir = get_voices_dir() voice_id = args.id or str(uuid.uuid4())[:8] voice_dir = os.path.join(voices_dir, voice_id) os.makedirs(voice_dir, exist_ok=True) # Normalize text: replace newlines with spaces text = _normalize_text(args.text) instruct = _normalize_text(args.instruct) if args.instruct else None if not instruct: raise ValueError("--instruct 参数是必需的,请提供语音风格描述") # Generate audio using TTS output_audio = os.path.join(voice_dir, "ref_audio.wav") ``` ```python sf.write(output_audio, audio_np, sample_rate) # Save reference text ref_text_path = os.path.join(voice_dir, "ref_text.txt") with open(ref_text_path, "w", encoding="utf-8") as f: f.write(text) # Save instruct instruct_path = os.path.join(voice_dir, "ref_instruct.txt") with open(instruct_path, "w", encoding="utf-8") as f: f. ...[truncated 2228 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict voice IDs to a conservative allowlist, for example: ```python import re VOICE_ID_RE = re.compile(r"^[A-Za-z0-9_-]{1,64}$") def validate_voice_id(voice_id: str) -> str: if not VOICE_ID_RE.fullmatch(voice_id): raise ValueError("Invalid voice ID") return voice_id ``` 2. Resolve and verify directory containment before every read or write: ```python voices_root = os.path.realpath(get_voices_dir()) voice_dir = os.path.realpath(os.path.join(voices_root, validate_voice_id(voice_id))) if os.path.commonpath([voices_root, voice_dir]) != voices_root: raise ValueError("Voice path escapes the voices directory") ``` 3. Reject absolute paths, path separators, `.` components, and `..` components even if an allowlist is introduced. 4. Apply the same centralized path-validation function to both `run_voice_create()` and `get_voice_path()`. 5. Refuse to overwrite an existing voice directory unless the user explicitly supplies a dedicated overwrite option. 6. Where supported, protect against symbolic-link traversal by checking each path component and using safe file-opening primitives with no-follow semantics. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/qwen-audio.py:37
Finding
Unpinned Dependencies and Automatic Runtime Package Installation<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qwen-audio.py:37-45`, `pyproject.toml:6-11`, and `references/env-check-list.md:9-28` **Vulnerability Type**: Unsafe dependency resolution and runtime installation **Risk Level**: Medium ### Vulnerable Code ```python def _ensure_mlx_audio() -> None: global _MLX_AUDIO_READY if _MLX_AUDIO_READY: return try: import mlx_audio # noqa: F401 except ImportError: print("mlx-audio 未安装,正在安装...", file=sys.stderr) os.system("uv add mlx-audio --prerelease=allow") import mlx_audio # noqa: F401 print("mlx-audio installed", file=sys.stderr) _MLX_AUDIO_READY = True ``` ```toml dependencies = [ "mlx-audio>=0.3.1; platform_system == 'Darwin'", "qwen-asr; platform_system != 'Darwin'", "qwen-tts>=0.1.1; platform_system != 'Darwin'", "torch; platform_system != 'Darwin'" ] ``` The mandatory environment checklist also instructs users to execute: ```text uv sync --prerelease=allow uv add mlx-audio --prerelease=allow ``` ### Technical Analysis Several executable dependencies are not pinned to exact reviewed versions. In particular, `qwen-asr` and `torch` have no version constraints, while `mlx-audio` and `qwen-tts` permit any later compatible release. The installation guidance additionally permits prerelease packages. On macOS, `_ensure_mlx_audio()` modifies the project environment during normal Skill execution by invoking `uv add`. The command's arguments are static, so this is not a shell-command injection issue. The risk instead arises because package resolution and installation occur at runtime using mutable remote package sources. Third-party Python packages execute code when imported and may also run build-related code during installation. Therefore, a compromised, malicious, or unexpectedly changed release can execute with the same privileges as the Skill. The code also ignores the return status from `os.system()` and immediat ...[truncated 1177 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove automatic package installation from `_ensure_mlx_audio()`. If the dependency is missing, fail safely with a clear installation message. 2. Pin all direct and transitive dependencies to reviewed versions through a committed lockfile. 3. Use hash verification for downloaded distributions where the package-management workflow supports it. 4. Do not enable prerelease resolution in normal setup instructions unless a specific, reviewed prerelease is strictly required. 5. Replace broad constraints with exact reviewed versions, while handling security updates through an explicit review and lockfile-update process. 6. Use `subprocess.run()` with an argument list and `check=True` for any unavoidable external command. Package installation should nevertheless remain a separate, user-approved setup operation rather than occur during TTS or STT execution. 7. Run dependency vulnerability and provenance checks in CI, and review changes to package versions and artifact hashes. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/qwen-audio.py:198
Finding
Third-Party Hugging Face Mirror Is Enabled by Default for Model Retrieval<![CDATA[ ## Vulnerability Details **File Location**: `scripts/qwen-audio.py:198-219`, `scripts/qwen-audio.py:901-913`, and `scripts/qwen-audio.py:973-974` **Vulnerability Type**: Unsafe default model supply-chain source **Risk Level**: Medium ### Vulnerable Code ```python def _can_reach_hf(endpoint: str, timeout_sec: float = 2.0) -> bool: url = endpoint.rstrip("/") if not url.startswith(("http://", "https://")): url = f"https://{url}" try: req = urllib.request.Request(url, method="HEAD") with urllib.request.urlopen(req, timeout=timeout_sec): return True except urllib.error.HTTPError: return True except Exception: return False def _configure_hf(args: argparse.Namespace) -> None: if args.hf_mirror: os.environ["HF_ENDPOINT"] = "https://hf-mirror.com" endpoint = os.environ["HF_ENDPOINT"] else: endpoint = os.environ.get("HF_ENDPOINT", "https://huggingface.co") if not _can_reach_hf(endpoint): os.environ["HF_HUB_OFFLINE"] = "1" print("! 无法连接 Hugging Face,已启用离线模式(仅使用本地模型)", file=sys.stderr) ``` ```python parser.add_argument( "--hf-mirror", dest="hf_mirror", action="store_true", help="使用 hf-mirror 镜像站(默认)", ) parser.add_argument( "--no-hf-mirror", dest="hf_mirror", action="store_false", help="不使用 hf-mirror 镜像站", ) parser.set_defaults(hf_mirror=True) ``` ```python args = parser.parse_args() _configure_hf(args) args.func(args) ``` Model loading then uses mutable model identifiers through `from_pretrained()`: ```python _QWEN_ASR_MODEL = Qwen3ASRModel.from_pretrained( model_name, dtype=torch_dtype, device_map=resolved_device, max_inference_batch_size=2, max_new_tokens=8192, forced_aligner=aligner_model, forced_aligner_kwargs={ "dtype": torch_dtype, "device_map": resolved_device, }, ) ``` ```python _QWEN ...[truncated 2498 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Make the official Hugging Face endpoint the default. 2. Require explicit user opt-in before using any third-party mirror. 3. Document all network destinations and explain that model artifacts may be downloaded when they are not locally cached. 4. Pin each model to an immutable reviewed commit or revision rather than using only a mutable repository name. 5. Maintain and verify cryptographic checksums for expected model artifacts where practical. 6. Prefer safe serialization formats and configure loaders to reject executable or pickle-based artifacts when supported. 7. Provide a strict offline mode that performs no reachability probe and fails clearly if a required model is unavailable locally. 8. Preserve a user-configured endpoint unless the user explicitly requests an override; do not silently replace it with a third-party mirror. ]]>
Vulnerability Patterns
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The documented purpose presents the skill as a straightforward audio library, but the observed behavior includes persistent on-disk profile management, subtitle generation, network reachability logic, and runtime package installation via shell. This mismatch can mislead users and security reviewers about the actual trust and execution model, especially because runtime dependency installation and network behavior materially expand the attack surface.

Context-Inappropriate Capability

High
Confidence
98% confidence
Finding
The skill automatically installs a dependency at runtime via a shell command when mlx-audio is missing. In a skill/plugin context this is especially risky because simply invoking the functionality can modify the host environment and execute unreviewed installer logic, expanding the attack surface to package repositories, mirrors, and local shell/PATH hijacking.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Content
import mlx_audio  # noqa: F401
    except ImportError:
        print("mlx-audio 未安装,正在安装...", file=sys.stderr)
        os.system("uv add mlx-audio --prerelease=allow")
        import mlx_audio  # noqa: F401
        print("mlx-audio installed", file=sys.stderr)
    _MLX_AUDIO_READY = True
Confidence
97% confidence
Finding
The code invokes a shell command at runtime to install a package when an import fails. Executing package installation through os.system is dangerous because it implicitly trusts the runtime environment, PATH resolution, package indexes, and shell execution context, which can lead to arbitrary code execution or supply-chain compromise if a malicious package, wrapper, or environment is present.

Lp3

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding
The skill exposes capabilities that imply shell, network, environment access, and file read/write behavior but does not declare any tool scope or permission boundaries in the manifest. This increases the risk of over-broad execution in a host agent environment, because consumers cannot tell what the skill may invoke or restrict it to least privilege.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The voice cloning feature instructs users to provide reference audio and transcript without any consent, authorization, or privacy safeguards. In this skill context, that omission is materially dangerous because the core functionality enables impersonation or unauthorized reuse of a person's voice using supplied samples.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
Automatically installing packages without explicit confirmation is unsafe operational behavior and can surprise users by executing networked install logic and modifying the environment. In combination with shell invocation and unpinned dependency resolution, the lack of a confirmation gate materially increases the risk of unintended code execution and supply-chain exposure.

Description-Behavior Mismatch

Medium
Confidence
89% confidence
Finding
The manifest says the skill is a high-performance audio library with TTS and STT, but this code also creates and maintains a persistent `voices/` directory, generates stored voice assets, and enumerates previously saved voice profiles. That is broader than plain TTS/STT processing and represents additional stateful voice-management behavior not reflected in the description.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The TTS command sets `--language` to `English` by default, which imposes a specific language choice when the user does not explicitly select one. This is a natural-language policy concern because the skill does not offer neutral auto-detection or require user opt-in for the language default.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The STT command sets `--language` to `English` by default, which imposes a specific language/locale assumption even when the user does not request it. The file does not provide an opt-in mechanism or documented justification for this enforced default.

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The `voice create` subcommand defaults `--language` to `English`, creating a fixed language assumption for generated voice assets. This is a policy issue because the skill enforces a specific language setting without explicit user choice or justification.

Missing User Warnings

Low
Confidence
83% confidence
Finding
The STT workflow writes transcriptions and subtitle files to disk but does not warn that input audio and output text may contain sensitive or private information. This can lead to accidental persistence or disclosure of confidential content, particularly when users save outputs to shared or predictable locations.

Description-Behavior Mismatch

Low
Confidence
98% confidence
Finding
The provided skill manifest describes this skill as a high-performance audio library with text-to-speech and speech-to-text capabilities, but the package metadata in this file says only "Add your description here." This is a clear description-behavior mismatch at the intent/documentation level because the code dependencies indicate an audio/STT/TTS package while the declared description is effectively empty and non-descriptive.

Unverifiable Dependency: torch has 16 known advisory(ies) (CVE-2025-2953 (PyTorch susceptible to local Denial of Service); CVE-2022-45907 (PyTorch vulnerable to arbitrary code execution); CVE-2025-32434 (PyTorch: `torch.load` with `weights_only=True` leads to remote code execution) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
# Best-effort sampling rate inference.
    for attr in ("sample_rate", "sr", "sampling_rate"):
        if hasattr(result, attr):
            return int(getattr(result, attr))
    for attr in ("sample_rate", "sr", "sampling_rate"):
        if hasattr(model, attr):
            return int(getattr(model, attr))
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Content
return int(getattr(result, attr))
    for attr in ("sample_rate", "sr", "sampling_rate"):
        if hasattr(model, attr):
            return int(getattr(model, attr))
    return 24000

def _normalize_text(text: str) -> str:
Confidence
50% confidence
Finding
Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Intent-Code Divergence

Low
Confidence
86% confidence
Finding
The parser description at `description="MLX-Audio CLI (TTS + STT)"` presents the tool as only supporting text-to-speech and speech-to-text. However, the same CLI defines separate `voice create` and `voice list` commands that persist and manage voice profiles, which contradicts that stated scope.

Static analysis

No suspicious patterns detected.