Back to skill

Security audit

Neomano TTS (ElevenLabs)

Security checks for vulnerabilities and agentic risk

Overview

This ElevenLabs text-to-speech skill is mostly coherent, but its helper script can overwrite any file path the running account can write.

Install only if you trust the agent invocations and can keep outputs constrained to a safe workspace directory. Avoid generating speech from sensitive text unless you are comfortable sending it to ElevenLabs, and consider fixing the helper to enforce a dedicated output directory and refuse overwrites before broad use.

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 (1)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/tts.py:64
Finding

Caller-Controlled Output Path Allows Arbitrary File Overwrite

Content
View full analysis

Vulnerability Details

File Location: scripts/tts.py:64-78
Vulnerability Type: Unrestricted file write and overwrite
Risk Level: Medium

Vulnerable Code

python
out_path = os.path.abspath(args.out)
os.makedirs(os.path.dirname(out_path), exist_ok=True)

try:
    with urllib.request.urlopen(req, timeout=120) as resp:
        audio = resp.read()
except Exception as e:
    print(f"ERROR: ElevenLabs request failed: {e}", file=sys.stderr)
    return 1

with open(out_path, "wb") as f:
    f.write(audio)

Technical Analysis

The --out argument is fully controlled by the caller. The script converts this value to an absolute path but does not verify that the resolved destination remains inside the workspace or another approved output directory.

os.makedirs() can create attacker-selected parent directories wherever the process has permission, while open(out_path, "wb") silently truncates an existing destination before writing the returned audio. There are also no checks for symbolic links or for whether the destination already exists.

Consequently, a caller who can influence the script arguments can direct a successful ElevenLabs response into any file writable by the account running the Skill. Converting a path with os.path.abspath() normalizes it but does not impose a security boundary.

The credential-related instructions in SKILL.md:9-15 and SKILL.md:27-34 do not independently constitute a vulnerability. They direct the operator to configure the required ElevenLabs API key in the runtime environment, and the implementation reads only the relevant environment variables. The script does not directly open or enumerate ~/.openclaw/.env.

Attack Path

  1. An attacker or untrusted caller supplies TTS text and chooses a sensitive writable path as --out, such as a user configuration file or an application-owned file.
  2. os.path.abspath(args.out) accepts the path without enforc ...[truncated 1052 chars]
Remediation
View remediation

Remediation Suggestions

  1. Define a fixed, trusted output root inside the workspace, such as workspace/media/elevenlabs-tts.
  2. Resolve both the trusted root and requested output path with pathlib.Path.resolve().
  3. Reject any destination that is not a descendant of the trusted root, using Path.relative_to() or an equivalent containment check.
  4. Reject symbolic-link destinations and inspect existing parent components to prevent symlink-based escapes.
  5. Use exclusive file creation mode ("xb") by default so existing files cannot be silently truncated. Require an explicit trusted overwrite option when replacement is genuinely necessary.
  6. Generate server-side filenames rather than accepting arbitrary absolute paths from untrusted callers.
  7. Apply restrictive file permissions and run the Skill under a dedicated, least-privileged service account.
  8. Validate the response status and content type before writing the response body, then write to a securely created temporary file within the approved directory and atomically rename it to the final destination.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (4)

Tainted flow: 'req' from os.environ.get (line 54, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/tts.py (reported line 69)May include surrounding context.

python
os.makedirs(os.path.dirname(out_path), exist_ok=True)

    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            audio = resp.read()
    except Exception as e:
        print(f"ERROR: ElevenLabs request failed: {e}", file=sys.stderr)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
80% confidence
Finding

The skill invokes a Python script that uses an environment variable and an external ElevenLabs service, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization gap: a runtime may permit broader execution, environment access, or network use than reviewers expect, increasing the chance of unintended data exposure or misuse.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

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

Human setup (one-time)

  1. Create or edit ~/.openclaw/.env on the machine running OpenClaw.
  2. Add your credentials (do not commit these):
bash

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/tts.py (reported line 42)May include surrounding context.

python
print("ERROR: missing voice id (set ELEVENLABS_VOICE_ID or pass --voice-id)", file=sys.stderr)
        return 2

    url = f"https://api.elevenlabs.io/v1/text-to-speech/{args.voice_id}"

    payload = {
        "text": args.text,

Static analysis

No suspicious patterns detected.