Back to skill

Security audit

Voice Assistant

Security checks for vulnerabilities and agentic risk

Overview

The skill is a coherent voice assistant, but it exposes an unauthenticated network service that can use the operator's speech-provider keys and OpenClaw gateway.

Review this carefully before installing. Use it only on a trusted machine or network, bind the server to localhost, add authentication and Origin checks before exposing it, avoid using a highly privileged OpenClaw gateway, and assume microphone audio, transcripts, and generated text may be sent to Deepgram or ElevenLabs and written to logs.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (4)

T09 · Insecure Skill Coding Practices

Warning
Location
static/index.html:253
Finding
DOM-based cross-site scripting through unsanitized transcript rendering<![CDATA[ ## Vulnerability Details **File Location**: `static/index.html:253-260` **Vulnerability Type**: DOM-based cross-site scripting **Risk Level**: Medium ### Vulnerable Code ```javascript function addTranscript(role, text) { const el = document.getElementById("transcript"); const line = document.createElement("div"); line.className = "transcript-line"; line.innerHTML = `<span class="role ${role}">${role === "user" ? "You" : "Agent"}:</span>${text}`; el.appendChild(line); el.scrollTop = el.scrollHeight; } ``` ### Technical Analysis The `text` value is concatenated directly into `innerHTML`. It originates from WebSocket transcript messages containing either speech-to-text output or the OpenClaw gateway's generated response. Neither the server nor the browser sanitizes it before HTML parsing. An HTML payload containing an executable event handler, such as an image with an `onerror` attribute, will therefore be interpreted as markup instead of displayed as text. The system prompt's request that the model avoid formatting is not a security boundary and does not prevent a model, external tool result, or manipulated response from returning HTML. The `role` field is also interpolated into an HTML attribute. Although the current server supplies fixed roles, the client should not assume WebSocket message contents are intrinsically safe. ### Attack Path 1. A user or malicious data source causes the OpenClaw agent to return attacker-controlled HTML, for example by requesting that it repeat a crafted string exactly. 2. The server forwards the response in a transcript WebSocket message: `{"type":"transcript","role":"assistant","text":"<img src=x onerror='...'>"}`. 3. `addTranscript()` inserts the value through `innerHTML`. 4. The browser parses the injected element and executes its event handler in the voice assistant's origin. 5. The injected JavaScript can read displayed conversation content, alter the interface, make same-origin req ...[truncated 741 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Construct the transcript exclusively with DOM text nodes and fixed, validated role values: ```javascript function addTranscript(role, text) { const el = document.getElementById("transcript"); const line = document.createElement("div"); line.className = "transcript-line"; const roleSpan = document.createElement("span"); const safeRole = role === "user" ? "user" : "assistant"; roleSpan.className = `role ${safeRole}`; roleSpan.textContent = safeRole === "user" ? "You: " : "Agent: "; line.appendChild(roleSpan); line.appendChild(document.createTextNode(String(text))); el.appendChild(line); el.scrollTop = el.scrollHeight; } ``` Additionally: - Validate WebSocket message schemas and reject unexpected roles or non-string transcript fields. - Deploy a restrictive Content Security Policy, particularly without `unsafe-inline`. - Avoid using model instructions as a substitute for output encoding. - If formatted assistant output is required later, process it with a maintained HTML sanitizer using a minimal allowlist. ]]>

T05 · Unauthorized Access and Privilege Escalation

Error
Location
scripts/server.py:574
Finding
Unauthenticated network-exposed voice and agent WebSocket<![CDATA[ ## Vulnerability Details **File Location**: `scripts/server.py:566-578` and `scripts/server.py:607` **Vulnerability Type**: Missing authentication, missing WebSocket origin validation, and excessive network exposure **Risk Level**: High ### Vulnerable Code ```python @app.get("/health") async def health(): return { "status": "ok", "stt_provider": STT_PROVIDER, "tts_provider": TTS_PROVIDER, "gateway": GATEWAY_URL, } @app.websocket("/ws/voice") async def voice_ws(ws: WebSocket): await ws.accept() session = VoiceSession(ws) await session.start() ``` The server is then bound to every interface: ```python uvicorn.run(app, host="0.0.0.0", port=SERVER_PORT, log_level="info") ``` ### Technical Analysis Every WebSocket connection is accepted without authentication, authorization, rate limiting, connection limits, or validation of the `Origin` header. At the same time, the process listens on `0.0.0.0`, making it reachable from other systems whenever host firewall or container networking permits access. Each accepted connection creates provider clients that use the server operator's Deepgram or ElevenLabs credentials. Audio accepted from the unauthenticated client is sent to the configured speech-to-text service, and resulting text is submitted to the configured OpenClaw gateway. Generated output is then submitted to the text-to-speech provider. This means an untrusted client can consume paid provider resources and interact with the configured OpenClaw agent under the server's trust relationship. The documentation describes the gateway as exposing the same agent, context, tools, and memory, so the potential impact may extend beyond conversation if that gateway permits consequential agent actions. The endpoint also lacks WebSocket origin checks, enabling cross-site WebSocket hijacking from an attacker-controlled page when a victim can reach the service. The flagged Base64 operation at `scripts/server.p ...[truncated 2379 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Bind to loopback by default: ```python SERVER_HOST = os.getenv("VOICE_SERVER_HOST", "127.0.0.1") uvicorn.run(app, host=SERVER_HOST, port=SERVER_PORT, log_level="info") ``` - Require an unpredictable session token or authenticated user session before accepting a WebSocket. - Validate the `Origin` header against an explicit allowlist before calling `ws.accept()`. - Use TLS and secure WebSockets when access is permitted beyond loopback. - Add per-client connection limits, audio-size limits, idle timeouts, request quotas, and rate limiting. - Restrict the OpenClaw gateway credentials or policy to only the tools required for voice interactions. - Do not return `GATEWAY_URL` from a public health endpoint; return only basic liveness information. - Document that non-loopback deployment requires authentication, TLS, firewall rules, and a trusted reverse proxy. - Separate administrative configuration from externally reachable application endpoints. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/server.py:105
Finding
Sensitive conversation content written to plaintext application logs<![CDATA[ ## Vulnerability Details **File Location**: `scripts/server.py:105-107`, `scripts/server.py:189-192`, `scripts/server.py:437-439`, and `scripts/server.py:488` **Vulnerability Type**: Plaintext sensitive-data logging **Risk Level**: Medium ### Vulnerable Code Deepgram transcripts are logged in full: ```python if transcript and (is_final or speech_final): log.info(f"STT final: {transcript}") await self._transcript_queue.put(transcript) ``` ElevenLabs transcripts are also logged in full: ```python text = result.get("text", "").strip() if text: log.info(f"STT final: {text}") await self._transcript_queue.put(text) ``` The conversation loop logs the same user content again and logs part of the assistant response: ```python log.info(f"User: {transcript}") self.messages.append({"role": "user", "content": transcript}) ``` ```python log.info(f"Assistant: {full_response[:100]}...") ``` ### Technical Analysis Voice transcripts commonly contain personal, confidential, credential-related, medical, financial, or organizational information. The implementation writes user speech to logs twice in normal operation and writes the first 100 characters of assistant output. These records go to the process logging destination. Depending on deployment, they can be retained in terminal history, container logs, centralized logging services, service-manager journals, or cloud observability systems. Such systems often have broader access and longer retention than the live voice session. Logging complete conversation content is not necessary to implement streaming STT, gateway interaction, or TTS. It therefore exceeds minimum data exposure required by the declared functionality. ### Attack Path 1. A user speaks confidential information during an ordinary session. 2. The STT listener writes the complete transcript to the application log. 3. The conversation loop writes the transcript a second time. 4. The assistant's response prefix is also logged. 5. A ...[truncated 893 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove transcript and assistant-content logging from the default information-level configuration. - Log only non-content operational metadata, such as session identifiers, durations, byte counts, latency, and provider status. - If transcript debugging is indispensable, require an explicit opt-in development flag and display a clear privacy warning. - Redact likely secrets and cap retention even when debug logging is enabled. - Prevent conversation logs from being sent to general-purpose telemetry systems. - Configure restrictive log permissions, short retention, encryption at rest, and auditable access controls. - Use opaque, randomly generated session identifiers rather than user-derived values for correlation. ]]>

T08 · Insecure Dependencies

Note
Location
pyproject.toml:9
Finding
Non-reproducible dependency resolution due to unbounded minimum-version constraints and absent lockfile<![CDATA[ ## Vulnerability Details **File Location**: `pyproject.toml:9-14` **Vulnerability Type**: Dependency and software supply-chain hardening weakness **Risk Level**: Low ### Vulnerable Code ```toml dependencies = [ "fastapi>=0.115.0", "uvicorn[standard]>=0.34.0", "websockets>=14.0", "httpx>=0.28.0", "python-dotenv>=1.0.0", ] ``` No dependency lockfile is present in the audited project. ### Technical Analysis Every runtime dependency has only a minimum version and no upper bound or exact resolved version. Consequently, two installations of the same skill can receive different direct and transitive dependency versions. A newly released, compromised, incompatible, or vulnerable version can be selected automatically without a corresponding change to the reviewed project. No evidence was found that the named packages are typosquatted or intentionally malicious. The confirmed issue is the lack of reproducible dependency resolution, not the presence of a known malicious package. The risk is amplified by `uvicorn[standard]`, which resolves additional optional dependencies transitively. Because the project has no committed lockfile, reviewers cannot determine the exact package set that will be installed at execution time. ### Attack Path 1. The project is audited while one set of dependency versions is current. 2. A later installation runs `uv` dependency resolution without a lockfile. 3. The package index provides newer direct or transitive versions allowed by the broad constraints. 4. Those versions are installed without being represented in the audited source tree. 5. If one of the selected releases is compromised or contains a security regression, its code executes in the server process with the skill's privileges. ### Impact Assessment A compromised runtime dependency would execute with the same operating-system privileges as the voice assistant server. It could potentially access: - Deepgram and ElevenLabs API keys loaded into ...[truncated 357 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Generate and commit a `uv.lock` file containing reviewed direct and transitive versions. - Use frozen installation in deployment, such as `uv sync --frozen`. - Pin direct dependencies to tested versions or use narrowly bounded compatible ranges. - Review and update the lockfile through a controlled dependency-update process. - Run dependency vulnerability scanning in continuous integration. - Inspect optional dependency expansion from `uvicorn[standard]` and retain only components actually required. - Where supported by the deployment workflow, verify package hashes and use a trusted package-index configuration. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (22)

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd {baseDir}
cp .env.example .env
# Fill in your API keys and gateway URL
uv run scripts/server.py
# Open http://localhost:7860 and click the mic
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd {baseDir}
cp .env.example .env
# Fill in your API keys and gateway URL
uv run scripts/server.py
# Open http://localhost:7860 and click the mic
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd {baseDir}
cp .env.example .env
# Fill in your API keys and gateway URL
uv run scripts/server.py
# Open http://localhost:7860 and click the mic
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
```bash
cd {baseDir}
cp .env.example .env
# Fill in your API keys and gateway URL
uv run scripts/server.py
# Open http://localhost:7860 and click the mic
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Possible Typosquatting: 'uvicorn' resembles popular package 'gunicorn'

High
Category
Supply Chain
Confidence
70% confidence
Finding
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.

Credential Access

High
Category
Privilege Escalation
Content
# Configuration
# ---------------------------------------------------------------------------

load_dotenv(Path(__file__).parent.parent / ".env")

GATEWAY_URL = os.getenv("OPENCLAW_GATEWAY_URL", "http://localhost:4141/v1")
GATEWAY_MODEL = os.getenv("OPENCLAW_MODEL", "claude-sonnet-4-5-20250929")
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding
The skill clearly requires network access and handling of environment-provided secrets, but it does not declare any explicit tool scope or permissions boundary. That makes the operational trust model ambiguous and increases the risk that an agent may invoke the skill with broader capabilities than a user expects.

Missing User Warnings

Medium
Confidence
97% confidence
Finding
The skill sends microphone audio, transcripts, and synthesized speech through third-party STT/TTS providers, but the documentation does not prominently warn users that sensitive voice content may leave their machine and be processed externally. In a voice assistant context, this can expose personal, confidential, or regulated information without informed consent.

External Transmission

Medium
Category
Data Exfiltration
Content
class ElevenLabsSTT:
    """STT via ElevenLabs Scribe API (REST-based, non-streaming)."""

    API_URL = "https://api.elevenlabs.io/v1/speech-to-text"

    def __init__(self):
        self._audio_buffer = bytearray()
Confidence
93% confidence
Finding
The ElevenLabs STT integration sends captured user speech to an external API, which is a real data egress path for potentially sensitive voice content. In a real-time voice assistant, spoken input can contain credentials, personal data, or confidential business information, so external transmission materially increases privacy and compliance exposure.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
This server forwards raw microphone audio, transcripts, and generated text to third-party STT/TTS providers and to the OpenClaw gateway, but this file shows no consent, disclosure, or privacy guardrails before doing so. In a voice assistant context, that creates a real privacy and data-handling risk because users may unknowingly transmit sensitive spoken content to external services.

External Transmission

Medium
Category
Data Exfiltration
Content
try:
            async with httpx.AsyncClient(timeout=10.0) as client:
                resp = await client.get(
                    "https://api.elevenlabs.io/v1/voices",
                    headers={"xi-api-key": ELEVENLABS_KEY},
                )
                resp.raise_for_status()
Confidence
60% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

External Transmission

Medium
Category
Data Exfiltration
Content
await self._resolve_voice_id()

        url = (
            f"https://api.elevenlabs.io/v1/text-to-speech/{self._voice_id}/stream"
            f"?output_format=pcm_16000"
        )
        try:
Confidence
94% confidence
Finding
The ElevenLabs TTS integration sends model output text to a third-party service for speech synthesis, creating another external data egress point. Because assistant responses may include echoed user data, summaries of sensitive input, or internal information from the gateway, this can leak sensitive content beyond the primary LLM boundary.

External Transmission

Medium
Category
Data Exfiltration
Content
async def synthesize_stream(self, text: str) -> AsyncGenerator[bytes, None]:
        """Stream audio bytes from Deepgram Aura for a given text chunk."""
        url = f"https://api.deepgram.com/v1/speak?model={TTS_VOICE_DG}&encoding=linear16&sample_rate={SAMPLE_RATE}"
        try:
            async with httpx.AsyncClient(timeout=30.0) as client:
                async with client.stream(
Confidence
94% confidence
Finding
The Deepgram TTS integration transmits generated text to an external provider, which is a real confidentiality risk if responses contain sensitive or regulated information. In this voice pipeline, every assistant utterance becomes outbound data to another vendor, increasing the overall exposure surface.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The page captures live microphone audio and immediately streams raw PCM to a backend WebSocket, but the UI only says 'Click the mic to start' and 'Listening...' without clearly warning that speech is being transmitted off-device for remote STT/agent processing. In a voice-assistant skill, users may reasonably understand the mic is active, but they may not understand that audio leaves the browser and is sent to external services, creating a meaningful privacy and consent risk.

Natural-Language Policy Violations

Low
Confidence
81% confidence
Finding
The configuration example sets `VOICE_TTS_VOICE_DG=aura-2-theia-en`, which hard-codes an English voice variant. The document does not explain that this is only an example default, offer locale choice at this point, or justify an English-only constraint for a region-specific use case.

Unverifiable Dependency: fastapi has 3 known advisory(ies) (CVE-2021-32677 (Cross-Site Request Forgery (CSRF) in FastAPI); CVE-2021-32677 (FastAPI is a web framework for building APIs with Python 3.6+ based on standard ); CVE-2024-24762 (FastAPI is a web framework for building APIs with Python 3.8+ based on standard )), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: uvicorn has 4 known advisory(ies) (CVE-2020-7694 (Log injection in uvicorn); CVE-2020-7695 (HTTP response splitting in uvicorn); CVE-2020-7694 (This affects all versions of package uvicorn. The request logger provided by the) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: websockets has 4 known advisory(ies) (CVE-2018-1000518 (websockets is vulnerable to denial of service by memory exhaustion); CVE-2021-33880 (Observable Timing Discrepancy in aaugustin websockets library); CVE-2018-1000518 (aaugustin websockets version 4 contains a CWE-409: Improper Handling of Highly C) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: httpx has 2 known advisory(ies) (CVE-2021-41945 (Improper Input Validation in httpx); CVE-2021-41945 (Encode OSS httpx <=1.0.0.beta0 is affected by improper input validation in `http)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: python-dotenv has 2 known advisory(ies) (CVE-2026-28684 (python-dotenv: Symlink following in set_key allows arbitrary file overwrite via ); CVE-2026-28684 (python-dotenv reads key-value pairs from a .env file and can set them as environ)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Unverifiable Dependency: pytest has 2 known advisory(ies) (CVE-2025-71176 (pytest has vulnerable tmpdir handling); CVE-2025-71176 (pytest has vulnerable tmpdir handling)), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
40% confidence
Finding
Dependency has known vulnerabilities (CVEs). Using packages with unpatched security flaws exposes the environment to known exploits.

Natural-Language Policy Violations

Low
Confidence
72% confidence
Finding
The configured system prompt instructs the assistant to never use markdown, bullet points, or code blocks, imposing a fixed output format regardless of user preference. This is a natural-language constraint embedded in the file and does not provide any opt-in or override path for users who may want structured responses.

Static analysis

No suspicious patterns detected.