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

Content
View full analysis

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
View 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"
    )
  1. 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")
  1. Explicitly reject absolute paths, path separators, . components, and .. components.
  2. Apply the same validation and containment checks to get_voice_path().
  3. Refuse to overwrite existing profiles by default. Require an explicit, documented replacement option when replacement is intended.
  4. Consider using exclusive file creation and restrictive filesystem permissions for stored voice data.
  5. 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

Content
View full analysis

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
View 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

Content
View full analysis

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
View 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.

Content

No source excerpt is available for this finding.

os.system() or os exec-family call

High
Category
Dangerous Code Execution
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.

Content

Scanner excerpt · scripts/mlx-audio.py (reported line 26)May include surrounding context.

python
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

Context-Inappropriate Capability

High
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

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.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/mlx-audio.py (reported line 65)May include surrounding context.

python
# 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))

Dynamic attribute access via getattr()

Low
Category
Dangerous Code Execution
Confidence
50% confidence
Finding

Dynamic getattr() with a non-literal attribute name can access arbitrary object attributes, potentially bypassing access controls.

Content

Scanner excerpt · scripts/mlx-audio.py (reported line 68)May include surrounding context.

python
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:

Missing User Warnings

Low
Category
Not specified by scanner
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.

Content

No source excerpt is available for this finding.