Back to skill

Security audit

Elevenlabs Toolkit

Security checks for vulnerabilities and agentic risk

Overview

The skill is mostly an ElevenLabs voice helper, but it also exposes paid API-backed endpoints without access controls and includes an under-disclosed Mistral chat data flow.

Review carefully before installing. Use this only in a trusted, authenticated application, add caller authentication, quotas, request-size limits, and rate limits before exposing the router, and remove or explicitly disclose the Mistral story-chat feature and MISTRAL_API_KEY handling. Do not print API keys in terminals or logs.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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
Findings (3)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/elevenlabs_api.py:32
Finding

Unauthenticated endpoints expose the server-held ElevenLabs API key and paid quota to unauthorized callers

Content
View full analysis

Vulnerability Details

File Location: scripts/elevenlabs_api.py, lines 32-46
Vulnerability Type: Missing authentication and authorization on a privileged API proxy
Risk Level: High

Vulnerable Code

python
@router.post("/api/voice/tts")
async def text_to_speech(body: dict):
    text = body.get("text", "")
    voice_id = body.get("voice_id", "pNInz6obpgDQGcFmaJgB")  # Adam default
    if not text:
        raise HTTPException(status_code=400, detail="text is required")
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.post(f"{BASE_URL}/text-to-speech/{voice_id}",
            headers=_headers(),
            json={"text": text, "model_id": "eleven_multilingual_v2",
                  "voice_settings": {"stability": 0.5, "similarity_boost": 0.75}})
        if r.status_code != 200:
            raise HTTPException(status_code=r.status_code, detail=r.text[:500])
        return StreamingResponse(iter([r.content]), media_type="audio/mpeg",
            headers={"Content-Disposition": "attachment; filename=narration.mp3"})

The same access-control weakness affects the voice-list, STT, sound-generation, lullaby-generation, WebSocket streaming, and story-chat routes.

Technical Analysis

The route accepts requests without authenticating or authorizing the caller and then authenticates the upstream request using the server-held ELEVENLABS_API_KEY. This turns the application into an unrestricted proxy for paid ElevenLabs operations.

The WebSocket route has the same issue and calls await websocket.accept() without verifying the caller. If the containing FastAPI application is exposed beyond a strictly trusted local environment, remote users can invoke privileged upstream functionality without possessing the ElevenLabs credential.

Although the code does not disclose the raw key in normal responses, it grants callers the effective privilege associated with that ke ...[truncated 1299 chars]

Remediation
View remediation

Remediation Suggestions

  • Require authentication on every HTTP endpoint before performing any upstream request.
  • Apply authorization policies so users can access only the operations and quotas assigned to them.
  • Authenticate WebSocket clients before calling websocket.accept(), or immediately reject unauthorized handshakes using an appropriate close code.
  • Add per-user and per-IP rate limits, concurrent-request limits, and spending or character quotas.
  • Bind the service to loopback by default if it is intended exclusively for local use.
  • Place the service behind an authenticated reverse proxy when remote access is required.
  • Record caller identity, operation type, and upstream usage in security audit logs without recording API keys or sensitive content.
  • Fail securely when ELEVENLABS_API_KEY is missing rather than sending an empty credential upstream.

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/elevenlabs_api.py:49
Finding

Unbounded uploads and generation inputs permit memory exhaustion and paid-resource denial of service

Content
View full analysis

Vulnerability Details

File Location: scripts/elevenlabs_api.py, lines 49-59
Vulnerability Type: Missing request-size and resource-consumption limits
Risk Level: Medium

Vulnerable Code

python
@router.post("/api/voice/stt")
async def speech_to_text(file: UploadFile = File(...)):
    audio_data = await file.read()
    async with httpx.AsyncClient(timeout=30) as client:
        r = await client.post(f"{BASE_URL}/speech-to-text",
            headers={"xi-api-key": ELEVENLABS_API_KEY},
            files={"file": (file.filename or "audio.wav", audio_data, file.content_type or "audio/wav")},
            data={"model_id": "scribe_v1"})
        if r.status_code != 200:
            raise HTTPException(status_code=r.status_code, detail=r.text[:500])
        return r.json()

Related routes also accept unrestricted text, caller-controlled generation durations, and an unrestricted sequence of WebSocket messages.

Technical Analysis

await file.read() reads the entire uploaded object into process memory without checking its declared or actual size. A sufficiently large upload, or multiple concurrent uploads, can consume worker memory and cause process termination or severe service degradation.

The TTS route does not limit text length. The sound and lullaby routes take duration_seconds directly from caller-controlled JSON without local bounds. The streaming route does not cap message size, cumulative text, session duration, or concurrent connections.

Upstream timeouts limit how long individual HTTP calls may wait, but they do not prevent local buffering, request floods, oversized WebSocket sessions, or paid upstream resource consumption.

Attack Path

  1. An attacker reaches the unauthenticated service.
  2. The attacker submits one or more very large multipart files to /api/voice/stt.
  3. Each application worker buffers the complete file through await file.read().
  4. Concurrent buf ...[truncated 763 chars]
Remediation
View remediation

Remediation Suggestions

  • Enforce maximum request-body and multipart-upload sizes at both the reverse proxy and application layers.
  • Check upload size while reading in bounded chunks and stop immediately when the configured limit is exceeded.
  • Stream supported uploads to the upstream provider rather than buffering entire files in memory.
  • Validate file types using trusted content inspection where practical; do not rely exclusively on the caller-provided MIME type or filename.
  • Replace untyped dict request bodies with validated Pydantic models.
  • Set strict minimum and maximum values for duration_seconds.
  • Limit TTS and prompt text by characters and encoded byte size.
  • Limit WebSocket message size, cumulative session text, session duration, idle time, and concurrent connections.
  • Add per-user/IP rate limits and global concurrency limits.
  • Configure explicit monthly and per-operation spending controls for the upstream account.

other

Warning
Location
scripts/elevenlabs_api.py:134
Finding

User messages containing child-related information may be sent to an undisclosed Mistral service

Content
View full analysis

Vulnerability Details

File Location: scripts/elevenlabs_api.py, lines 134-170
Vulnerability Type: Undisclosed third-party data processing and excessive external access
Risk Level: Medium

Vulnerable Code

python
@router.post("/api/story/chat")
async def story_concierge(body: dict):
    message = body.get("message", "")
    conversation_id = body.get("conversation_id", None)
    if not message:
        raise HTTPException(status_code=400, detail="message is required")
    
    # Use ElevenLabs Conversational AI API
    async with httpx.AsyncClient(timeout=30) as client:
        payload = {
            "model_id": "eleven_turbo_v2_5",
            "text": message,
            "voice_settings": {"stability": 0.5, "similarity_boost": 0.75}
        }
        
        # Story concierge system prompt via Mistral
        from mistralai import Mistral
        mistral_key = os.environ.get("MISTRAL_API_KEY", "")
        if mistral_key:
            mclient = Mistral(api_key=mistral_key)
            resp = mclient.chat.complete(
                model="mistral-large-latest",
                messages=[
                    {"role": "system", "content": "You are the Story Concierge for Sandman Tales, a multilingual bedtime story app. Help parents refine their story idea. Ask about: child's name, age, favorite themes (animals, space, ocean), language preference, story mood (adventurous, calming, funny). Keep responses warm, brief (2-3 sentences), and conversational. When you have enough info, summarize the story brief."},
                    {"role": "user", "content": message}
                ]
            )
            concierge_text = resp.choices[0].message.content.strip()
        else:
            concierge_text = f"I'd love to help create a bedtime story! Tell me about the child - what's their name, and what do they love? (animals, space, magic?)"
        
        return {
   
...[truncated 2334 chars]
Remediation
View remediation

Remediation Suggestions

  • Remove the story-chat route if it is not required for the declared ElevenLabs voice functionality.
  • If retained, explicitly declare Mistral as an outbound provider and document MISTRAL_API_KEY as an optional dependency.
  • Clearly notify users before transmitting messages to Mistral and obtain appropriate consent.
  • Avoid requesting a child's real name or other unnecessary identifying information; use aliases and age ranges where possible.
  • Redact or minimize personal information before external transmission.
  • Make Mistral integration opt-in and disabled by default.
  • Document data retention, processing location, and provider privacy terms.
  • Add authentication and authorization to the route.
  • Correct comments and capability documentation so they accurately identify the service receiving the data.
  • Remove the unused ElevenLabs-style payload and unnecessary httpx.AsyncClient block to make the external data flow unambiguous.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (17)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

A description-behavior mismatch is security-relevant because it prevents reviewers from understanding what the skill actually does and what secrets or external services it may access. The reported undeclared Mistral dependency/chat capability, if real, would materially expand data exposure and attack surface beyond the stated ElevenLabs-only purpose.

Content

No source excerpt is available for this finding.

External Script Fetching

High
Category
Supply Chain
Confidence
90% confidence
Finding

Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.

Content

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

To get the full current list from the API:

bash
curl -s -H "xi-api-key: $ELEVENLABS_API_KEY" https://api.elevenlabs.io/v1/voices | python3 -m json.tool

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares outbound network use and environment-variable access in metadata, but it does not define an explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity: consumers may not realize the skill needs secret access and network egress, which weakens review and policy enforcement.

Content

No source excerpt is available for this finding.

Behavior Manipulation

Medium
Category
Prompt Injection
Confidence
70% confidence
Finding

Subtle instructions detected that may alter agent decision-making or introduce hidden biases.

Content

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

md
## Known Voice IDs

These are confirmed voices used in OpenClaw workflows. Always prefer these over browsing the full list:

| Voice | Voice ID | Best For |
|---|---|---|

Internal Network Request

Medium
Category
Server-Side Request Forgery
Confidence
70% confidence
Finding

Code issues a request to a loopback, link-local, or private-range host. This can reach internal services not meant to be exposed and is a common SSRF pivot.

Content

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

md
import httpx

try:
    httpx.get("http://localhost:8000/health", timeout=2.0)
except httpx.ConnectError:
    # Server is not running — start it first
    import subprocess

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The documentation recommends spawning a server process with subprocess.Popen, which expands the skill from API usage into local process execution. That increases risk because users may run unreviewed code or background services based on documentation guidance, creating a broader execution surface than the stated skill purpose suggests.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The documentation explicitly suggests echoing and retrieving the API key without warning about shell history, terminal capture, CI logs, or copy-paste leakage. Secrets disclosed this way can be reused to incur costs, access account data, or generate unauthorized content via the ElevenLabs account.

Content

No source excerpt is available for this finding.

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 · SKILL.md (reported line 89)May include surrounding context.

md
router = APIRouter()

ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY", "")
BASE_URL = "https://api.elevenlabs.io/v1"

def _headers():
    return {"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"}

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 · SKILL.md (reported line 227)May include surrounding context.

md
router = APIRouter()

ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY", "")
BASE_URL = "https://api.elevenlabs.io/v1"

def _headers():
    return {"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"}

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/elevenlabs_api.py (reported line 13)May include surrounding context.

python
router = APIRouter()

ELEVENLABS_API_KEY = os.environ.get("ELEVENLABS_API_KEY", "")
BASE_URL = "https://api.elevenlabs.io/v1"

def _headers():
    return {"xi-api-key": ELEVENLABS_API_KEY, "Content-Type": "application/json"}

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The TTS and STT endpoints transmit user-provided text and uploaded audio to ElevenLabs, but the code provides no visible disclosure, consent flow, or privacy notice. In a voice-enabled app, these payloads may contain personal or sensitive data, so silent third-party transmission creates privacy and compliance risk.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The WebSocket endpoint forwards live user text streams directly to ElevenLabs without any visible notice or consent mechanism. Streaming content can include highly sensitive in-the-moment user input, making undisclosed real-time external transmission more risky than one-shot requests.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The skill is presented as an ElevenLabs voice toolkit, but the story-chat endpoint invokes Mistral for unrelated LLM behavior. This creates an undeclared data flow to a second external provider and expands the trust and privacy boundary beyond what users and integrators would reasonably expect.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The code reads and uses MISTRAL_API_KEY even though the skill is scoped as an ElevenLabs integration. Pulling in a second provider credential without clear justification increases secret exposure, enlarges the attack surface, and can cause sensitive user prompts to be sent to an unexpected third party.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

Telling users to retrieve the API key from 1Password introduces secret-manager interaction outside the narrow API-integration purpose and can normalize broader secret access than necessary. Even as documentation, it can encourage workflows that expose or mishandle credentials in terminals or logs.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

The file reads ELEVENLABS_API_KEY and later MISTRAL_API_KEY from environment variables to authenticate external requests. There is no docstring, comment, or other warning in the file explaining that the skill depends on sensitive credentials or that those credentials will be used to contact third-party services.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The function comment claims use of ElevenLabs Conversational AI, but the implementation actually generates responses with Mistral. This mismatch is a security-relevant transparency issue because reviewers and operators may approve or deploy the code under a false assumption about where user data is processed.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.