Back to skill

Security audit

audiobook

Security checks for vulnerabilities and agentic risk

Overview

The skill does what it advertises, but its audiobook workflow can fetch arbitrary URLs and forward the resulting text to external TTS services without enough scoping or privacy warning.

Review this skill carefully before installing or following its code. Use it only with public, non-sensitive text unless you add URL validation, explicit provider consent, secure temporary files, and a clear choice of TTS provider. Do not run the examples as root or use private/internal URLs without additional safeguards.

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

Error
Location
SKILL.md:31
Finding

Unrestricted URL Fetching Enables SSRF and External Disclosure of Sensitive Content

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 31-46 and 127-218
Vulnerability Type: Server-Side Request Forgery, local file access, and sensitive-data transmission
Risk Level: High

Vulnerable Code

The source URL is passed directly to curl without validating its scheme, destination, resolved IP address, port, or redirects:

python
def fetch_article_content(url):
    """Fetch article content using curl for reliability."""
    # Use curl to get raw HTML - more reliable than web fetch tools
    result = subprocess.run(
        ["curl", "-s", url],
        capture_output=True,
        text=True
    )
    html = result.stdout

    # Strip HTML tags (basic approach)
    import re
    text = re.sub(r'<script[^>]*>.*?</script>', '', html, flags=re.DOTALL)
    text = re.sub(r'<style[^>]*>.*?</style>', '', html, flags=re.DOTALL)
    text = re.sub(r'<[^>]+>', ' ', text)
    text = re.sub(r'\s+', ' ', text).strip()

    return text

The resulting content is subsequently transmitted to external TTS providers. For example, the OpenAI implementation sends every text chunk in the request body:

python
def create_with_openai(text, output_path):
    """Generate audiobook using OpenAI TTS API."""
    api_key = os.environ.get("OPENAI_API_KEY")

    chunks = chunk_text(text, max_chars=4000)
    audio_files = []

    for i, chunk in enumerate(chunks):
        chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            "https://api.openai.com/v1/audio/speech",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json"
            },
            json={
                "model": "tts-1",
                "input": chunk,
                "voice": "onyx",
                "response_format": "mp3"
            }
        )

        if
...[truncated 4236 chars]
Remediation
View remediation

Remediation Suggestions

  1. Accept only https URLs and reject all other schemes.
  2. Reject URLs containing embedded credentials, unexpected ports, malformed hosts, or ambiguous IP representations.
  3. Resolve the hostname before connecting and reject loopback, private, link-local, multicast, reserved, and unspecified IPv4 and IPv6 addresses.
  4. Repeat destination validation after every redirect to prevent redirect-based SSRF and DNS rebinding bypasses.
  5. Restrict curl explicitly, for example with --proto '=https' and --proto-redir '=https', or replace it with an HTTP client implementing equivalent controls.
  6. Apply connection timeouts, total request timeouts, response-size limits, and maximum redirect counts.
  7. Consider an allowlist of approved content hosts where operationally feasible.
  8. Keep local-file and internal-network access unavailable to the content-fetching component through sandboxing and outbound network policy.
  9. Clearly disclose that all cloud TTS options, including gTTS, transmit the complete source text to an external provider.
  10. Require explicit user confirmation before transmitting local files, private URLs, authenticated content, or potentially confidential text.
  11. Offer an offline TTS option for sensitive content.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:135
Finding

Predictable Shared Temporary Files Permit Symlink-Based File Clobbering

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 135-144, 175-184, 214-215, and 230-246
Vulnerability Type: Insecure temporary-file creation and symlink attack
Risk Level: Medium

Vulnerable Code

Each provider writes audio chunks to predictable names in the globally shared /tmp directory:

python
for i, chunk in enumerate(chunks):
    chunk_file = f"/tmp/chunk_{i:03d}.mp3"

    response = requests.post(
        f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
        headers={
            "xi-api-key": api_key,
            "Content-Type": "application/json"
        },
        json={
            "text": chunk,
            "model_id": "eleven_turbo_v2_5",
            "voice_settings": {"stability": 0.5, "similarity_boost": 0.75}
        }
    )

    if response.status_code == 200:
        with open(chunk_file, "wb") as f:
            f.write(response.content)
        audio_files.append(chunk_file)

The same predictable chunk-file pattern is used by the OpenAI and gTTS implementations:

python
chunk_file = f"/tmp/chunk_{i:03d}.mp3"

Audio concatenation uses another fixed temporary path:

python
def concatenate_audio(audio_files, output_path):
    """Concatenate multiple audio files using ffmpeg."""
    if not audio_files:
        return False

    # Create file list for ffmpeg
    list_file = "/tmp/audio_list.txt"
    with open(list_file, "w") as f:
        for audio_file in audio_files:
            f.write(f"file '{audio_file}'\n")

    # Concatenate with ffmpeg
    result = subprocess.run([
        "ffmpeg", "-y", "-f", "concat", "-safe", "0",
        "-i", list_file, "-c", "copy", output_path
    ], capture_output=True)

    # Cleanup temp files
    import os
    for f in audio_files:
        os.unlink(f)
    os.unlink(list_file)

    return result.returncode == 0

Technical Analysis

/tmp is ...[truncated 2564 chars]

Remediation
View remediation

Remediation Suggestions

  1. Create a unique private directory for each invocation with tempfile.TemporaryDirectory().
  2. Ensure the directory has restrictive permissions and is owned by the executing account.
  3. Generate chunk and list files inside that private directory rather than directly under /tmp.
  4. Use securely randomized names and exclusive creation where appropriate.
  5. When opening security-sensitive files, use no-follow semantics and verify that the opened object is a regular file.
  6. Place all cleanup in try/finally blocks or rely on the temporary-directory context manager.
  7. Avoid predictable shared state between concurrent audiobook jobs.
  8. Run the workflow as a dedicated unprivileged account rather than root.
  9. Validate the caller-supplied output path and avoid overwriting existing files unless the user explicitly authorizes replacement.
  10. Prefer passing safely created file descriptors where supported, reducing pathname race conditions.
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (9)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill sends fetched or user-provided text to third-party TTS providers (ElevenLabs, OpenAI, and potentially Google via gTTS) but does not clearly warn users that their content leaves the local environment. This creates a meaningful privacy and data-governance risk, especially if users process sensitive documents or non-public web content.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The manifest describes content fetching, text processing, and TTS conversion, but the implementation guidance explicitly invokes shell-accessed system tools via subprocess.run. Spawning external programs is a materially broader capability than ordinary HTTP fetching or audio processing libraries, and the manifest does not disclose this execution capability.

Content

No source excerpt is available for this finding.

External Transmission

Medium
Category
Data Exfiltration
Confidence
96% confidence
Finding

This code sends text content and the ElevenLabs API key to an external TTS service. While functionally necessary for remote TTS, it creates a genuine confidentiality risk if users are unaware that fetched or local text is being uploaded to a third party.

Content

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

md
for i, chunk in enumerate(chunks):
        chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={
                "xi-api-key": api_key,

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

The hardcoded ElevenLabs API endpoint indicates transmission of content to an external service. By itself the URL is not dangerous, but within this skill it confirms third-party data flow that should be treated as a privacy/security concern requiring disclosure and user choice.

Content

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

md
chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            f"https://api.elevenlabs.io/v1/text-to-speech/{voice_id}",
            headers={
                "xi-api-key": api_key,
                "Content-Type": "application/json"

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This is effectively the same OpenAI external transmission event already flagged above. It is a true privacy-relevant issue because text content is uploaded to a third-party API, though the behavior is consistent with the advertised cloud TTS functionality.

Content

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

md
for i, chunk in enumerate(chunks):
        chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            "https://api.openai.com/v1/audio/speech",
            headers={
                "Authorization": f"Bearer {api_key}",

External Transmission

Medium
Category
Data Exfiltration
Confidence
90% confidence
Finding

This is effectively the same OpenAI external transmission event already flagged above. It is a true privacy-relevant issue because text content is uploaded to a third-party API, though the behavior is consistent with the advertised cloud TTS functionality.

Content

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

md
for i, chunk in enumerate(chunks):
        chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            "https://api.openai.com/v1/audio/speech",
            headers={
                "Authorization": f"Bearer {api_key}",

External Transmission

Medium
Category
Data Exfiltration
Confidence
88% confidence
Finding

The OpenAI API endpoint confirms that text is sent to an external TTS service. The security concern is not the literal URL but the undisclosed outbound transfer of potentially sensitive content to a third party.

Content

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

md
chunk_file = f"/tmp/chunk_{i:03d}.mp3"

        response = requests.post(
            "https://api.openai.com/v1/audio/speech",
            headers={
                "Authorization": f"Bearer {api_key}",
                "Content-Type": "application/json"

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The gTTS example hard-codes lang='en', which imposes a specific language/locale choice. The file does not indicate that this is optional, user-selectable, or justified as a region-specific tool, so it conflicts with the policy against forcing a language without opt-in.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

While concatenating audio is consistent with audiobook generation, doing so by invoking ffmpeg via subprocess.run gives the skill the ability to execute local binaries. That execution capability is not mentioned in the manifest, which frames the skill as a content-fetching and TTS conversion tool.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.