Back to skill

Security audit

Qwen3 Audio

Security checks for vulnerabilities and agentic risk

Overview

The skill is a plausible local audio TTS/STT tool, but it needs review because it can install unpinned code at runtime, uses a third-party model mirror by default, and has an unsafe voice-profile path that can write outside its intended folder.

Review this before installing in an environment with sensitive files or credentials. Use only with trusted inputs, avoid custom voice IDs containing paths, prefer a pre-provisioned locked dependency environment, and opt out of the third-party Hugging Face mirror unless you explicitly trust it. Treat created voice profiles as sensitive retained data.

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/mlx-audio.py:501
Finding
Path Traversal and Arbitrary File Overwrite Through Voice Profile ID## Vulnerability Details **File Location**: `scripts/mlx-audio.py:501-543` **Vulnerability Type**: Unsanitized path construction and arbitrary file overwrite **Risk Level**: High ### Vulnerable Code ```python 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") # Run TTS to generate the audio (always use VoiceDesign model for voice creation) _ensure_mlx_audio() from mlx_audio.tts.utils import load_model as load_tts_model model = load_tts_model(DEFAULT_VOICEDESIGN_MODEL) kwargs = { "text": text, "language": args.language, "instruct": instruct, } results = list(model.generate(**kwargs)) if not results: raise RuntimeError("TTS 生成失败:未返回音频结果") result = results[0] sample_rate = _get_sample_rate(result, model) audio = result.audio sf.write(output_audio, audio, 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.write(instruct) ``` ### Technical Analysis The user-controlled `--id` argument is used directly as a path component without validation or canonical containment checking. Python's `os.path.join()` does not guarantee that the resulting path remains under `voices_dir`: - An absolute `voice_id` causes the preceding `voices_dir` component to be discarded. - A relative value containing `../` can traverse outside the intended `voices/` directory. - Existing directories are accepted because `os.makedirs(..., exist_ok ...[truncated 1907 chars]
Remediation
## Remediation Suggestions 1. Restrict voice IDs to a conservative allowlist, for example: ```python import re VOICE_ID_PATTERN = re.compile(r"^[A-Za-z0-9_-]{1,64}$") if not VOICE_ID_PATTERN.fullmatch(voice_id): raise ValueError( "Voice ID may contain only letters, digits, underscores, and hyphens" ) ``` 2. Canonicalize the base and candidate paths and enforce containment: ```python voices_dir = os.path.realpath(get_voices_dir()) voice_dir = os.path.realpath(os.path.join(voices_dir, voice_id)) if os.path.commonpath([voices_dir, voice_dir]) != voices_dir: raise ValueError("Voice path escapes the voices directory") ``` 3. Explicitly reject absolute paths, path separators, `.` components, and `..` components. 4. Apply the same validation and containment checks to `get_voice_path()`. 5. Refuse to overwrite existing profiles by default. Require an explicit, documented replacement option when replacement is intended. 6. Consider using exclusive file creation and restrictive filesystem permissions for stored voice data. 7. Add tests covering absolute paths, `../` traversal, nested paths, symlink-based escapes, and existing-profile replacement.

T08 · Insecure Dependencies

Warning
Location
scripts/mlx-audio.py:17
Finding
Automatic Installation and Immediate Execution of an Unpinned Dependency## Vulnerability Details **File Location**: `scripts/mlx-audio.py:17-29` **Vulnerability Type**: Unsafe runtime dependency installation **Risk Level**: Medium ### Vulnerable Code ```python _MLX_AUDIO_READY = False 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 安装完成", file=sys.stderr) _MLX_AUDIO_READY = True ``` The project dependency is also specified with a mutable lower bound in `pyproject.toml:7`: ```toml dependencies = [ "mlx-audio>=0.3.1", ] ``` ### Technical Analysis When `mlx_audio` is unavailable, ordinary Skill execution invokes the package manager and immediately imports the downloaded package. The installation permits prerelease packages and is not constrained by a reviewed lockfile, an exact version, or verified artifact hashes. Although the command passed to `os.system()` is constant and therefore does not expose direct shell-command injection in the reviewed code, it creates a supply-chain execution boundary. The effective code executed by the Skill can change after the Skill itself has been audited. The use of `os.system()` also fails to verify the package manager's exit status before attempting the import and mutates project dependency metadata during normal feature execution. ### Attack Path 1. The Skill runs in an environment where `mlx_audio` is not installed. 2. `_ensure_mlx_audio()` catches `ImportError`. 3. The Skill executes `uv add mlx-audio --prerelease=allow`. 4. `uv` resolves and downloads a currently available package version and its transitive dependencies. 5. The Skill immediately imports `mlx_audio`. 6. Package initialization code executes with the same filesystem, environment, network, and process p ...[truncated 882 chars]
Remediation
## Remediation Suggestions 1. Remove package installation from runtime feature paths. If the dependency is unavailable, exit with a clear setup instruction. 2. Install dependencies only during an explicit, user-approved setup phase. 3. Commit a lockfile containing reviewed, exact dependency versions and transitive resolutions. 4. Pin `mlx-audio` to a reviewed version rather than using only `>=0.3.1`. 5. Avoid prerelease packages unless a documented compatibility requirement makes them necessary. 6. Where supported, verify package artifacts using trusted hashes. 7. If an installation command remains necessary, use: ```python subprocess.run( ["uv", "sync", "--frozen"], check=True, ) ``` This avoids shell interpretation, checks failure status, and prevents unreviewed dependency resolution when used with a committed lockfile. 8. Keep installation and execution in a least-privileged environment without unnecessary credentials or access to sensitive directories.

T08 · Insecure Dependencies

Warning
Location
scripts/mlx-audio.py:42
Finding
Third-Party Model Mirror Is Enabled by Default Without Artifact Pinning## Vulnerability Details **File Location**: `scripts/mlx-audio.py:42-53` and `scripts/mlx-audio.py:605-617` **Vulnerability Type**: Unsafe external model source and mutable model dependency **Risk Level**: Medium ### Vulnerable Code ```python 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) ``` The selected endpoint is contacted by the following code at `scripts/mlx-audio.py:31-40`: ```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 ``` ### Technical Analysis Every command calls `_configure_hf()`, and the default behavior sets `HF_ENDPOINT` to `https://hf-mirror.com`, a third-party mirror rather than the official Hugging Face endpoint. Downstream model-loading functions can subsequently retrieve model artifacts from this endpoint. The explicit connectivity check sends only a `HEAD` request. The reviewed check does not itself attach audio, text, local files, environment variables, or authentication headers. Ther ...[truncated 2059 chars]
Remediation
## Remediation Suggestions 1. Default to the official endpoint, `https://huggingface.co`. 2. Require explicit user opt-in before using a third-party mirror. 3. Clearly document the mirror operator, trust implications, transmitted request metadata, and credential-handling expectations. 4. Pin every model to an immutable reviewed commit or revision rather than relying on a mutable repository name. 5. Verify downloaded model and configuration artifacts against trusted hashes or signed metadata. 6. Ensure authentication tokens are never forwarded to third-party mirrors unless the user has explicitly authorized that behavior. 7. Prefer offline operation after an explicit model-provisioning step. 8. Allow users to configure an approved endpoint rather than silently replacing `HF_ENDPOINT`. 9. Consider separating the connectivity probe from normal local-only operations so commands do not make unnecessary outbound requests when all required artifacts are already present.
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (13)

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description presents a local audio TTS/STT library, but the documented behavior extends to package installation, network endpoint configuration, persistent local storage of voice profiles, and generation of additional output artifacts. This mismatch is dangerous because reviewers and users may approve the skill expecting limited audio processing while it actually performs broader system and network actions, including persistence of potentially sensitive biometric voice data.

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 安装完成", file=sys.stderr)
    _MLX_AUDIO_READY = True
Confidence
98% confidence
Finding
The script executes a shell command at runtime to install a package whenever `mlx_audio` is missing. This introduces arbitrary code execution and supply-chain risk because package resolution and install-time scripts are triggered automatically, and it happens without strong validation or an explicit security gate. In the context of an audio skill, runtime shell-based package installation is not necessary for core TTS/STT behavior and materially increases danger.

Context-Inappropriate Capability

High
Confidence
97% confidence
Finding
The skill performs automatic package installation via a shell command during normal execution, which creates a supply-chain entry point unrelated to safe handling of local audio inputs. If a package source, dependency, installer hook, or environment is compromised, running the skill can execute untrusted code on the host.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises executable capabilities including shell, network, environment access, and file read/write, but does not declare any explicit tool scope such as permissions or allowed-tools. This creates an underconstrained execution surface where a host agent may grant broader access than users expect, increasing the chance of unintended command execution, file modification, or network access during normal use.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The skill manifest context says this skill is a high-performance audio library for Apple Silicon with TTS and STT, but the package metadata description is only the placeholder text "Add your description here". This creates a clear mismatch between the documented purpose and the declared package description, undermining accurate developer intent disclosure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The code automatically installs a package when import fails and only prints a status message; it does not obtain explicit consent or present the security implications of downloading and executing third-party code. This weakens user control and can lead to unexpected code execution and network access in environments that assume local-only processing.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
Voice creation persistently stores reference audio, transcription text, and style instructions under the skill directory, which can expose sensitive biometric or personal data if users do not realize the data is retained. In a voice-cloning context, this is more sensitive than ordinary output because the retained assets can enable later impersonation or privacy harm.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The TTS command sets `--language` to `English` by default rather than prompting the user or making language selection explicit. This is a natural-language locale policy concern because the skill imposes a language choice without opt-in or justification.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The STT command defaults `--language` to `English`, which imposes a language setting on users without explicit selection. Per the policy criteria, locale or language should not be forced unless the constraint is justified or user-selectable by design.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The `voice create` subcommand assigns `English` as the default language, again enforcing a language preference without user opt-in. This creates the same locale-policy issue present in the other subcommands.

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.

Missing User Warnings

Low
Confidence
82% confidence
Finding
The TTS flow writes synthesized audio to `args.output`, and later code paths also write transcript and subtitle files to disk. These file writes are not accompanied by comments, docstrings, or consistently clear user-facing warnings about persistent output creation, which matters because audio/transcript data may be sensitive.

Static analysis

No suspicious patterns detected.