Back to skill

Security audit

AI语音合成TTS - 聚合数据

Security checks for vulnerabilities and agentic risk

Overview

This skill is a straightforward text-to-speech integration, but users should handle the API key and downloaded audio files carefully.

Prefer setting JUHE_SPEECH_KEY through your environment or platform secrets instead of using --key or scripts/.env. Do not synthesize confidential text unless you are comfortable sending it to Juhe, and use --download/--output only for paths you intend to create or replace.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/speech_generate.py:106
Finding

Unvalidated Server-Controlled Audio Download URL

Content
View full analysis

Vulnerability Details

File Location: scripts/speech_generate.py:106-110, 133-138, 320-337
Vulnerability Type: Unrestricted remote resource retrieval and unsafe file download
Risk Level: Medium

python
if error_code == 0:
    result = data.get("result", {})
    return {
        "success": True,
        "orderid": result.get("orderid", ""),
        "audio_url": result.get("audio_url", ""),
    }
python
def download_audio(url: str, save_path: str) -> bool:
    """Download the audio file locally and return whether it succeeded."""
    try:
        with urllib.request.urlopen(url, timeout=30) as resp:
            content = resp.read()
        Path(save_path).write_bytes(content)
        return True
    except Exception as e:
        print(f"Download failed: {e}")
        return False
python
audio_url = result["audio_url"]
orderid = result["orderid"]

print(f"Audio synthesis succeeded.")
print(f"Order ID: {orderid}")
print(f"Audio URL:")
print(f"{audio_url}")

if parsed["download"] or parsed["output"]:
    if parsed["output"]:
        save_path = parsed["output"]
    else:
        timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
        save_path = f"speech_{timestamp}.wav"

    if download_audio(audio_url, save_path):
        size_kb = Path(save_path).stat().st_size // 1024
        print(f"Download completed. File size: {size_kb} KB")

Technical Analysis

The remote Juhe API controls the audio_url value. When downloading is enabled, the script passes this value directly to urllib.request.urlopen without validating:

  • The URL scheme
  • The destination hostname
  • Redirect destinations
  • Response size
  • Response content type
  • Whether the response is actually an audio file

The script also reads the complete response into memory before writing it. A compromised provider, compromised API response path, or provider-side de ...[truncated 2040 chars]

Remediation
View remediation

Remediation Suggestions

  1. Parse the URL before making the request and require the https scheme.
  2. Allowlist the exact expected audio-storage hostname or a narrowly defined set of provider-owned hostnames.
  3. Disable automatic redirects or validate the scheme and hostname of every redirect target.
  4. Reject URLs containing credentials, unexpected ports, or ambiguous hostname representations.
  5. Check Content-Type against expected audio media types, while recognizing that this is only a supplementary control.
  6. Enforce a conservative maximum download size using Content-Length when available and a strict byte counter while streaming.
  7. Stream the response in bounded chunks rather than calling resp.read() without a limit.
  8. Write to a securely created temporary file and atomically move it into place after validation.
  9. Refuse to overwrite an existing destination by default and reject symbolic-link destinations.
  10. Use a restrictive file mode for generated files and report validation failures without attempting the write.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/speech_generate.py:172
Finding

API Key Exposure Through Command-Line Arguments

Content
View full analysis

Vulnerability Details

File Location: scripts/speech_generate.py:172-176, 294-297; documented at SKILL.md:27-31
Vulnerability Type: Sensitive credential exposure through process arguments and shell history
Risk Level: Low

python
if arg == "--key":
    if i + 1 < len(args):
        result["cli_key"] = args[i + 1]
        i += 2
    else:
        result["error"] = "Error: --key requires an API key value"
        return result
python
if not api_key:
    print("API key not found. Configure it using one of these methods:")
    print("1. Environment variable: export JUHE_SPEECH_KEY=your_api_key")
    print("2. .env file: create .env in the script directory")
    print("3. Command-line argument: python speech_generate.py --key your_api_key \"text\"")
    sys.exit(1)

The documentation also explicitly recommends the following supported method:

bash
python scripts/speech_generate.py --key yourAppKey "Text to synthesize"

Technical Analysis

Supplying an API key through --key places the secret in the process argument vector. Depending on the operating system and execution environment, process arguments may be visible to other local users, process-monitoring tools, job runners, audit systems, or diagnostic collectors. The command may also remain in shell history and terminal logs after execution.

The API request itself uses HTTPS and sending the key to the declared Juhe endpoint is necessary for the TTS operation. The vulnerability is the additional local exposure caused by accepting and documenting a command-line secret.

Attack Path

  1. A user follows the documented command and supplies the Juhe API key through --key.
  2. The shell stores the command in history, or the operating system exposes it in the process list while the request is running.
  3. Another local user, monitoring service, CI log collector, support bundle, or later reader of shell history ...[truncated 589 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove the --key option and its examples from both the script and SKILL.md.
  2. Prefer a protected environment variable or a credential file readable only by the owning user.
  3. If interactive entry is necessary, use a non-echoing prompt such as Python's getpass.getpass.
  4. Ensure the .env file is excluded from version control and recommend permissions equivalent to 0600.
  5. Avoid printing, logging, or embedding the key in exception messages.
  6. Document credential rotation procedures for users who previously supplied the key on the command line.
  7. Where supported, use a scoped credential restricted to only the required TTS API and apply usage or spending limits.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (9)

Credential Access

High
Category
Privilege Escalation
Confidence
87% confidence
Finding

The skill recommends storing the API key in a local .env file under the scripts directory, which is a credential-handling pattern that can lead to accidental exposure through repository inclusion, backup leakage, or other file access by tools. In a skill that already uses file and environment capabilities, colocating secrets with runnable content increases the blast radius if the workspace is exposed.

Content

Scanner excerpt · SKILL.md (reported line 27)May include surrounding context.

md
# 方式一:环境变量(推荐,一次配置永久生效)
export JUHE_SPEECH_KEY=你的AppKey

# 方式二:.env 文件(在脚本目录创建)
echo "JUHE_SPEECH_KEY=你的AppKey" > scripts/.env

# 方式三:每次命令行传入

Credential Access

High
Category
Privilege Escalation
Confidence
83% confidence
Finding

The documented command-line usage includes passing the API key directly as a --key argument, which can expose credentials in shell history, process listings, logs, and agent telemetry. This is a genuine secret leakage risk, especially in automated environments where command invocations may be recorded.

Content

Scanner excerpt · SKILL.md (reported line 28)May include surrounding context.

md
export JUHE_SPEECH_KEY=你的AppKey

# 方式二:.env 文件(在脚本目录创建)
echo "JUHE_SPEECH_KEY=你的AppKey" > scripts/.env

# 方式三:每次命令行传入
python scripts/speech_generate.py --key 你的AppKey "今天天气真好!"

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/speech_generate.py (reported line 17)May include surrounding context.

python
API Key 配置(任选其一,优先级从高到低):
    1. 环境变量: export JUHE_SPEECH_KEY=your_api_key
    2. 脚本同目录的 .env 文件: JUHE_SPEECH_KEY=your_api_key
    3. 直接传参: python speech_generate.py --key your_api_key "文本内容"

免费申请 API Key: https://www.juhe.cn/docs/api/id/830

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/speech_generate.py (reported line 296)May include surrounding context.

python
API Key 配置(任选其一,优先级从高到低):
    1. 环境变量: export JUHE_SPEECH_KEY=your_api_key
    2. 脚本同目录的 .env 文件: JUHE_SPEECH_KEY=your_api_key
    3. 直接传参: python speech_generate.py --key your_api_key "文本内容"

免费申请 API Key: https://www.juhe.cn/docs/api/id/830

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/speech_generate.py (reported line 70)May include surrounding context.

python
if env_key:
        return env_key

    env_file = Path(__file__).parent / ".env"
    if env_file.exists():
        for line in env_file.read_text(encoding="utf-8").splitlines():
            line = line.strip()

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill documents and requires capabilities that include environment access, file reads/writes, and outbound network use, but it does not declare any explicit tool scope or permissions boundary. This increases the chance that an agent invokes the skill with broader-than-necessary privileges, making misuse or unintended data access harder to constrain and audit.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The description contains broad natural-language trigger phrases like '帮我生成一段语音' and '用英文女声读一下这句话', which can overlap with common user requests and cause over-triggering. In an agent setting, this may route unrelated or sensitive text into the skill unexpectedly, leading to accidental third-party disclosure or unintended API use.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The skill does not present a clear warning that user-provided text will be transmitted to a third-party TTS provider. Because users may submit private, regulated, or proprietary text for synthesis, the lack of disclosure creates a real risk of unintentional external data sharing.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
94% confidence
Finding

该脚本的自然语言说明、示例和运行时交互文本均固定为中文,且没有提供语言/locale 选择开关。根据规则,强制特定语言而无用户选择可能构成自然语言组织政策违规,尤其是当技能并未声明仅面向中文用户时。

Content

No source excerpt is available for this finding.