Back to skill

Security audit

TtsCN

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed Chinese text-to-speech tool that uses cloud TTS providers, local audio files, and optional voice cloning in ways that match its stated purpose.

Install only if you are comfortable sending synthesis text and any clone reference audio to the selected TTS provider. Use voice cloning only with voices you own or have permission to use, be aware MiniMax cloning can be paid, and choose output paths carefully because existing files may be overwritten.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (24)

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
84% confidence
Finding
Although shell injection is not present, this ffmpeg concat operation consumes a concat manifest containing unescaped filenames and explicitly enables '-safe 0'. If an attacker can influence output_file/out_dir or create crafted colliding filenames in that directory, ffmpeg may read unintended files or misparse entries, making the concat stage more dangerous than the other subprocess calls.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in wav_part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
84% confidence
Finding
The concat step uses ffmpeg with '-safe 0' and a generated concat manifest, which weakens ffmpeg path-safety checks. Although the listed files are based on locally created temp names, output_file controls out_dir/cwd and the code writes manifest lines without escaping embedded quotes, so unusual filesystem paths or attacker-influenced directories could cause unintended file interpretation by ffmpeg.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
80% confidence
Finding
This concat-stage subprocess is not shell-injected, but it drives ffmpeg's concat demuxer with a generated list file while explicitly setting -safe 0. Because output paths are influenced by output_file/out_dir and list entries are written without escaping embedded quotes, a crafted filename could break the concat file format and potentially cause ffmpeg to read unintended local files within the working directory context.

Tainted flow: 'url' from os.environ.get (line 33, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
payload_dict["group_id"] = group_id
            payload = json.dumps(payload_dict)

        resp = requests.post(url, headers=headers, data=payload, timeout=120)
        if resp.status_code != 200:
            raise RuntimeError(
                f"MiniMax API error {resp.status_code}: {resp.text[:300]}"
Confidence
89% confidence
Finding
The request destination is derived from the MINIMAX_API_BASE environment variable and then used with a Bearer API key, allowing credentialed requests to be redirected to an arbitrary host if the environment is compromised or attacker-controlled. In a skill/plugin context, this can exfiltrate sensitive text and API credentials to unintended endpoints.

Tainted flow: 'content' from requests.get (line 108, network input) → requests.post (network output)

Medium
Category
Data Flow
Content
filename = os.path.basename(audio)

    print("  Uploading reference audio ...", file=sys.stderr)
    r = requests.post(f"{MINIMAX_BASE}/files/upload", headers=headers,
                      data={"purpose": "voice_clone"},
                      files={"file": (filename, content)}, timeout=300)
    if r.status_code != 200:
Confidence
87% confidence
Finding
If --audio is an http(s) URL, the tool downloads arbitrary remote content and then re-uploads it to MiniMax. This creates an SSRF-style fetch-and-forward primitive and can be abused to pull internal or sensitive resources reachable by the host and exfiltrate them to a third party.

Tainted flow: 'MINIMAX_BASE' from os.environ.get (line 44, credential/environment) → requests.post (network output)

Critical
Category
Data Flow
Content
vid = voice_id or _minimax_voice_id(name)
    print(f"  Creating clone '{vid}' ...", file=sys.stderr)
    r = requests.post(f"{MINIMAX_BASE}/voice_clone",
                      headers={**headers, "Content-Type": "application/json"},
                      json={"file_id": file_id, "voice_id": vid}, timeout=300)
    if r.status_code != 200:
Confidence
94% confidence
Finding
MINIMAX_BASE is fully overrideable via the MINIMAX_API_BASE environment variable, and the same Authorization bearer token is sent to that URL. If an attacker can influence the environment, they can redirect requests to an attacker-controlled server and capture the API key and uploaded audio.

Lp3

Medium
Category
MCP Least Privilege
Confidence
93% confidence
Finding
The skill clearly uses sensitive capabilities including shell execution, filesystem reads/writes, environment-variable access, and networked API calls, yet declares no permissions or safety boundaries. That creates a transparency and policy-enforcement gap: an agent or user may invoke the skill without understanding that it can open browsers, access local files, persist data, and transmit content to third-party TTS providers.

Tp4

High
Category
MCP Tool Poisoning
Confidence
89% confidence
Finding
The published description frames the skill as straightforward Chinese TTS, but the documented behavior extends into voice cloning, persistent storage of cloned identities, idempotency caches, schema/introspection endpoints, and documentation generation. This mismatch can mislead operators about the data handling and execution surface, reducing informed consent and making risky features like cloning appear incidental rather than primary.

Vague Triggers

Medium
Confidence
84% confidence
Finding
The auto-activation rules are broad enough to trigger on general discussion of TTS providers, pricing, features, or related keywords, even when the user may only want information rather than tool execution. Overbroad activation increases the chance of unnecessary tool use, unintended browser launches, provider selection, or transmission of user text to external services.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The skill instructs the agent to ALWAYS open a local HTML page in the user's browser first, without an explicit consent step. Unsolicited browser launching is a side effect that can surprise users, leak intent in shared environments, and normalize automatic execution of local content without review.

Missing User Warnings

Low
Confidence
95% confidence
Finding
The workflow reiterates automatic browser opening as a default step when the user is browsing or comparing providers, again without a warning or approval checkpoint. Repetition of this behavior makes unintended side effects more likely in agent-driven environments where tools may be executed eagerly.

Missing User Warnings

Medium
Confidence
87% confidence
Finding
The backend sends text chunks to Microsoft's Edge TTS service over the network, but this file contains no user-facing disclosure or consent mechanism. If the text contains sensitive content, users may unknowingly exfiltrate private data to a third-party cloud service, which is a real privacy and compliance risk in a TTS skill context.

Missing User Warnings

Medium
Confidence
84% confidence
Finding
The backend sends input text to Tencent Cloud using long-lived API credentials, but this file provides no user-facing notice, consent, or minimization controls. In a TTS skill, input may contain sensitive or regulated text, so silent transmission to a third-party cloud service creates a real privacy and compliance risk even if it is functionally expected.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
The code base64-encodes and transmits the provided text to Xunfei's external WebSocket TTS service, which is a real data exposure concern if users may submit sensitive content. Base64 is not protection, and this file contains no consent, redaction, or warning mechanism about third-party processing.

Missing User Warnings

Medium
Confidence
74% confidence
Finding
The code writes full synthesis results to a persistent cache in the user's home directory without any apparent consent flow, disclosure, or file-permission hardening. If synthesis results contain sensitive text, audio paths, or provider metadata, another local user or process may recover that information from disk for up to 7 days, making this a privacy and data-handling weakness in the TTS context.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
67% confidence
Finding
The ffmpeg invocation passes a potentially user-controlled output_file directly as a command argument without validating that it is a safe output path. While this is not shell injection because subprocess is called safely, it can still enable arbitrary file overwrite or writing outside intended directories if an attacker controls output_file, especially since ffmpeg is run with -y to overwrite existing files.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
86% confidence
Finding
The concat list is built with raw basenames inside single quotes, but filenames are not escaped for ffmpeg's concat demuxer syntax, and '-safe 0' weakens path safety checks. In a multi-user or attacker-influenced directory, crafted filenames or symlink/race interference could cause unintended file inclusion or processing failures.

Unvalidated Output Injection

High
Category
Output Handling
Content
for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        import subprocess
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
78% confidence
Finding
The code passes output_file directly to ffmpeg without validating or constraining the destination path. If an attacker can control output_file, they may overwrite arbitrary files writable by the process or cause output to be written to unexpected locations, which is especially relevant because ffmpeg is invoked with -y to overwrite existing files without prompting.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
63% confidence
Finding
The concat operation runs ffmpeg with '-safe 0' on a concat list file assembled from filesystem paths. Although the code uses basenames for temporary parts, disabling ffmpeg's safety checks and relying on ad hoc quoting increases risk if an attacker can influence output paths or filenames elsewhere, potentially causing unintended file interpretation or access during concat processing.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
76% confidence
Finding
The concat step invokes ffmpeg with '-safe 0' and builds a concat manifest using filenames derived from the working directory. While shell injection is still mitigated by argv usage, disabling ffmpeg's safety checks and trusting local path material can widen exposure to path confusion or unintended file inclusion if an attacker can influence the output directory or collide with temporary filenames in that directory.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in wav_part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
85% confidence
Finding
The concat workflow feeds ffmpeg a manifest file built from filenames and explicitly disables ffmpeg's safety checks with '-safe 0'. Because manifest entries are not escaped and ffmpeg concat parsing has its own syntax rules, attacker-influenced path content or working directories could lead to unintended file inclusion or processing beyond the intended temporary chunks.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
72% confidence
Finding
The concat operation writes an ffmpeg concat manifest using unescaped filenames inside single quotes while also enabling '-safe 0'. If an attacker can influence output_file/out_dir or pre-place crafted matching files in that directory, this weak manifest handling can cause unintended file inclusion or unsafe path processing by ffmpeg, making the media-processing boundary more dangerous than a normal subprocess call.

Unvalidated Output Injection

High
Category
Output Handling
Content
with open(concat_list, "w", encoding="utf-8") as f:
            for pf in part_files:
                f.write(f"file '{os.path.basename(pf)}'\n")
        result = subprocess.run(
            ["ffmpeg", "-y", "-f", "concat", "-safe", "0",
             "-i", concat_list, "-c", "copy", output_file],
            capture_output=True, text=True, cwd=out_dir,
Confidence
84% confidence
Finding
Here the risk is not classic shell injection but unsafe injection into ffmpeg's concat input file. Since concat_list lines are built as file '<basename>' without escaping, a malicious basename containing quotes or newline characters could alter the concat script semantics, and -safe 0 further weakens ffmpeg's path restrictions.

Session Persistence

Medium
Category
Rogue Agent
Content
## Voice Cloning (`clone` command)

Create a custom voice from reference audio, store it under a name, then use
the name anywhere `--voice` is accepted. Built-in for **minimax** (local file
OK, 10s-5min audio, paid: ~$1.5/voice global site or ¥9.9 on first use China
site; a new clone is TEMPORARY until its first real synthesis — use it within
Confidence
86% confidence
Finding
The skill documents persistent storage of cloned voice records in ~/.ttsCN.json and caching under ~/.ttscn_idem/, creating session persistence that outlives a single interaction. In the context of voice cloning, persisted identifiers and related metadata can expose sensitive biometric-adjacent usage history, be reused unexpectedly, or remain on disk longer than the user realizes.

Static analysis

No suspicious patterns detected.