Back to skill

Security audit

Text To Speech

Security checks for vulnerabilities and agentic risk

Overview

The skill performs text-to-speech, but it may send submitted text to an external TTS service without clearly warning the user.

Review before installing if you may convert confidential, regulated, personal, or secret text. Use it only with text you are comfortable sending to a third-party TTS provider unless you verify an offline backend, and keep output paths inside a safe working directory.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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)

other

Warning
Location
scripts/tts.py:27
Finding
Undisclosed Transmission of User Text to an External TTS Service## Vulnerability Details **File Location**: `scripts/tts.py`, lines 27-32 **Vulnerability Type**: Undisclosed third-party data transmission **Risk Level**: Medium ### Vulnerable Code ```python def tts_gtts(text, output, lang='en'): """Use gTTS for TTS.""" try: from gtts import gTTS tts = gTTS(text, lang=lang) tts.save(output) ``` ### Technical Analysis The supplied text is passed to the `gTTS` library for network-backed speech synthesis. This causes user-controlled content to be transmitted to an external Google TTS service. The skill documentation describes text-to-speech conversion but does not disclose that submitted text leaves the local environment. This is particularly relevant because text submitted for narration may include confidential documents, personal information, internal business data, or other sensitive content. No consent, sensitivity warning, offline mode, or content filtering control is implemented. ### Attack Path 1. A user invokes the skill with text containing confidential or personal information. 2. `main()` passes the supplied text to `tts_gtts()`. 3. `gTTS(text, lang=lang)` prepares the content for remote synthesis. 4. `tts.save(output)` contacts the external service and sends the text as part of the synthesis request. 5. The external service may process, log, or retain the submitted content according to policies outside the user's local security boundary. ### Impact Assessment The issue does not grant local operating-system privileges or direct code execution. Its impact is loss of confidentiality for all text submitted to the skill. Exposure is limited to the supplied text and associated request metadata, but may include highly sensitive information depending on how the skill is used.
Remediation
## Remediation Suggestions - Clearly disclose in `SKILL.md` that the default backend sends supplied text to an external service. - Require explicit user consent before transmitting content. - Warn users not to submit secrets, credentials, regulated data, or confidential documents. - Provide an explicitly selected offline TTS backend for sensitive workloads. - Add a configuration option that disables all network-backed synthesis. - Document the external provider's applicable privacy, retention, and data-processing policies. - Consider implementing sensitivity checks or confirmation prompts before external transmission.

T08 · Insecure Dependencies

Note
Location
scripts/tts.py:34
Finding
Unpinned Third-Party Dependency Installation Guidance## Vulnerability Details **File Location**: `scripts/tts.py`, lines 34-38 **Vulnerability Type**: Insecure dependency management **Risk Level**: Low ### Vulnerable Code ```python except ImportError: print("gTTS not installed. Run: pip install gtts") return tts_say(text, output) except Exception as e: print(f"Error: {e}") ``` The module-level documentation also recommends the same unconstrained installation: ```python Note: Uses gTTS (pip install gtts) for free TTS, or falls back to macOS say command. ``` ### Technical Analysis The skill directs users to install `gtts` without specifying an audited version, cryptographic hashes, a lock file, or an approved package source. Consequently, dependency resolution retrieves whichever release is current when the command is executed. This creates a mutable supply-chain boundary: a future compromised or malicious package release could be installed without the skill itself changing. Unpinned installation also increases the risk of incompatible updates and makes audited builds difficult to reproduce. The reviewed project does not itself retrieve or install the package automatically, and there is no evidence that the named package is currently malicious. Exploitation therefore depends on the user following the displayed instruction and the resolved package or package-distribution channel being compromised. ### Attack Path 1. The script is executed in an environment where `gTTS` is absent. 2. The `ImportError` handler instructs the user to run `pip install gtts`. 3. The user executes the unconstrained installation command. 4. The package manager resolves a mutable release from its configured package index. 5. If the selected release, distribution artifact, index, or dependency has been compromised, attacker-controlled code may run during installation or when `gtts` is imported. 6. That code executes with the privileges of the user or ...[truncated 427 chars]
Remediation
## Remediation Suggestions - Declare `gTTS` in a version-controlled dependency file using a reviewed, exact version. - Use cryptographic hashes, such as pip's `--require-hashes`, to verify downloaded artifacts. - Generate and commit a lock file to make installations reproducible. - Document an approved package index and avoid untrusted extra indexes. - Install the dependency inside an isolated virtual environment with least privilege. - Add automated dependency vulnerability and integrity scanning. - Review and deliberately update the pinned version rather than resolving the latest release at runtime.
Vulnerability Patterns
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • 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 (6)

Unvalidated Output Injection

High
Category
Output Handling
Content
# Check if we're on macOS
        if sys.platform == 'darwin':
            # Convert to mp3 using afplay/ffmpeg if available
            subprocess.run(['say', '-o', output.replace('.mp3', '.aiff'), text], check=True)
            print(f"Saved to: {output}")
            return 0
        else:
Confidence
95% confidence
Finding
The output path is accepted without validation and is passed directly to the say command's -o option, allowing writes to arbitrary filesystem locations accessible to the current user. In an agent or automation context, a malicious user could overwrite or create files outside the intended working directory, causing data loss, tampering with local files, or interfering with other processes.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill demonstrates shell-based execution via `python scripts/tts.py ...` but does not declare any `permissions` or `allowed-tools` scope. This creates an authorization gap where an agent may invoke shell capabilities without an explicit least-privilege policy, increasing the risk of unintended command execution or misuse if user-controlled text or file paths are passed through the shell.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The module presents itself as converting text to audio, and the fallback function prints that it saved to the user-specified output path. However, the code invokes `say -o` with `output.replace('.mp3', '.aiff')`, so it writes a different file than the one reported unless the caller explicitly asked for an AIFF path. This is an active contradiction between the user-facing behavior/documentation and the actual file written.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Check if we're on macOS
        if sys.platform == 'darwin':
            # Convert to mp3 using afplay/ffmpeg if available
            subprocess.run(['say', '-o', output.replace('.mp3', '.aiff'), text], check=True)
            print(f"Saved to: {output}")
            return 0
        else:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Try macOS voices
    try:
        result = subprocess.run(['say', '-v', '?'], capture_output=True, text=True)
        if result.returncode == 0:
            print("\nmacOS Voices:")
            for line in result.stdout.strip().split('\n')[:20]:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
82% confidence
Finding
This code sets the default language to 'en' via the --lang argument, which imposes a specific locale unless the user overrides it. The policy allows locale constraints when users are given a choice or opt in, but here the default behavior is English-first rather than language-neutral.

Static analysis

No suspicious patterns detected.