Back to skill

Security audit

voiceclaw

Security checks for vulnerabilities and agentic risk

Overview

This voice assistant largely matches its stated purpose, but it can expose a local OpenClaw token through unauthenticated API endpoints or unsafe remote configuration.

Review before installing. Keep the service bound to 127.0.0.1, do not expose port 8788 or set HOST=0.0.0.0 without adding authentication and TLS, and do not point OPENCLAW_GATEWAY_URL or VOICEVOX_URL at untrusted remote services. Prefer an explicit, least-privilege OpenClaw token for this voice assistant, update the Node dependencies, and treat spoken commands and conversation history as data that will be sent to the local server and configured backends.

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
src/server.js:115
Finding
Unauthenticated API Endpoints Act as a Privileged OpenClaw Gateway Proxy<![CDATA[ ## Vulnerability Details **File Location**: `src/server.js:115-145`, `src/server.js:211-238` **Vulnerability Type**: Missing authentication and authorization on credential-backed proxy endpoints **Risk Level**: High when exposed beyond localhost; Medium under the default loopback-only configuration ### Vulnerable Code ```js // POST /api/chat-stream { messages } → sentence-level SSE // Each SSE event: data: {"sentence":"...", "done":false} or {"done":true,"fullText":"..."} app.post('/api/chat-stream', async (req, res) => { if (!OPENCLAW_GATEWAY_TOKEN) { return res.status(500).json({ ok: false, error: 'OPENCLAW_GATEWAY_TOKEN not set' }); } let messages; if (req.body?.messages?.length) { messages = req.body.messages; } else { const text = String(req.body?.text || '').trim(); if (!text) return res.status(400).json({ ok: false, error: 'text or messages required' }); messages = [{ role: 'user', content: text }]; } // SSE headers res.setHeader('Content-Type', 'text/event-stream'); res.setHeader('Cache-Control', 'no-cache'); res.setHeader('Connection', 'keep-alive'); res.flushHeaders(); const sendEvent = (obj) => res.write(`data: ${JSON.stringify(obj)}\n\n`); try { const upstream = await fetch(`${OPENCLAW_GATEWAY_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${OPENCLAW_GATEWAY_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: OPENCLAW_MODEL, user: 'voiceclaw', messages, stream: true }), }); ``` The non-streaming endpoint has the same access-control issue: ```js // POST /api/chat { text } → OpenClaw Gateway → { reply } app.post('/api/chat', async (req, res) => { try { if (!OPENCLAW_GATEWAY_TOKEN) { return res.status(500).json({ ok: false, error: 'OPENCLAW_GATEWAY_TOKEN not set' }); } // Accept { messages } (full history) or { text } (single turn) let messages; if (req.b ...[truncated 3780 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Require authentication and authorization for every `/api/*` endpoint. Use a separate application session credential rather than exposing a bearer-token-backed proxy to anonymous clients. 2. Generate a dedicated, least-privilege OpenClaw credential for voiceclaw. Restrict it to only the model and operations required for voice conversation. 3. Keep loopback binding as the default and fail closed if a non-loopback `HOST` is configured without explicit authentication and TLS settings. 4. For remote deployments, require HTTPS, an authenticated reverse proxy, restrictive firewall rules, and trusted-client access controls. 5. Validate `messages` as an array of bounded objects with explicitly permitted roles and string content. Reject unknown properties, excessive message counts, and oversized content. 6. Validate and bound TTS text and speaker parameters. 7. Add per-client rate limiting, concurrency limits, upstream timeouts, and response-size limits. 8. Apply restrictive CORS and origin checks as defense in depth, while not treating origin checks as a substitute for authentication. 9. Document that changing `HOST` or publishing the port crosses a security boundary and is unsafe without access controls. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
src/server.js:8
Finding
Auto-Discovered Gateway Token Can Be Transmitted to an Arbitrary or Cleartext Upstream<![CDATA[ ## Vulnerability Details **File Location**: `src/server.js:8-41`, `src/server.js:139-145`, `src/server.js:227-238` **Vulnerability Type**: Unsafe credential reuse and sensitive-data transmission to an unrestricted destination **Risk Level**: Medium ### Vulnerable Code The application automatically reads the local OpenClaw gateway token: ```js // --- OpenClaw config auto-discovery --- function loadOpenClawConfig() { // Search paths: ~/.openclaw/openclaw.json (Linux/macOS) const candidates = [ path.join(os.homedir(), '.openclaw', 'openclaw.json'), ]; for (const p of candidates) { try { const raw = fs.readFileSync(p, 'utf-8'); const cfg = JSON.parse(raw); const port = cfg?.gateway?.port; const token = cfg?.gateway?.auth?.token; if (port && token) { console.log(`[voiceclaw] OpenClaw config loaded from ${p}`); return { url: `http://127.0.0.1:${port}`, token }; } } catch { // file not found or parse error — try next } } return null; } const openclawAuto = loadOpenClawConfig(); const app = express(); app.use(express.json({ limit: '1mb' })); const HOST = process.env.HOST || '127.0.0.1'; const PORT = process.env.PORT ? Number(process.env.PORT) : 8788; const OPENCLAW_GATEWAY_URL = process.env.OPENCLAW_GATEWAY_URL || openclawAuto?.url || 'http://127.0.0.1:18789'; const OPENCLAW_GATEWAY_TOKEN = process.env.OPENCLAW_GATEWAY_TOKEN || openclawAuto?.token || ''; const VOICEVOX_URL = process.env.VOICEVOX_URL || 'http://127.0.0.1:50021'; ``` It then sends the selected token and complete message history to the selected URL: ```js const upstream = await fetch(`${OPENCLAW_GATEWAY_URL}/v1/chat/completions`, { method: 'POST', headers: { 'Authorization': `Bearer ${OPENCLAW_GATEWAY_TOKEN}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ model: OPENCLAW_MODEL, user: 'voiceclaw', messages, stream: true }), }); ``` The non-streaming endpoint performs the ...[truncated 3800 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind auto-discovered credentials to the auto-discovered loopback gateway URL. Never combine an overridden `OPENCLAW_GATEWAY_URL` with an auto-discovered token. 2. If the gateway URL is overridden, require `OPENCLAW_GATEWAY_TOKEN` to be supplied explicitly for that destination and terminate startup if it is absent. 3. Parse the destination with the standard `URL` class and permit only `http:` and `https:`. Reject embedded credentials and malformed or unexpected hosts. 4. Permit plain HTTP only for verified loopback addresses such as `127.0.0.1`, `::1`, or an explicitly approved local socket. 5. Require HTTPS for every non-loopback gateway and VOICEVOX destination. 6. Add an explicit allowlist for trusted gateway and VOICEVOX hosts where remote operation is necessary. 7. Warn prominently and fail closed when an auto-discovered token would cross a loopback or transport-security boundary. 8. Use a dedicated, revocable, least-privilege token for voiceclaw rather than the general gateway credential. 9. Correct `docs/architecture.md` to identify the gateway token as a secret and document where commands, history, responses, and TTS text are transmitted. 10. Avoid returning raw upstream response bodies in errors, because upstream services may include sensitive details. ]]>
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
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
Findings (16)

Credential Access

High
Category
Privilege Escalation
Content
cd voiceclaw
npm install

# Start (no .env needed if OpenClaw is running locally)
npm start
# → [voiceclaw] OpenClaw config loaded from ~/.openclaw/openclaw.json
# → [voiceclaw] listening on http://127.0.0.1:8788
Confidence
91% confidence
Finding
The skill explicitly states that a gateway token is auto-detected from ~/.openclaw/openclaw.json, which means it is designed to read local credential material from a user configuration file. Accessing credentials from a broad, implicit location can expose sensitive tokens to unintended code paths, logs, or downstream services if the skill is compromised or insufficiently sandboxed.

Known Vulnerable Dependency: path-to-regexp==0.1.12 — 1 advisory(ies): CVE-2024-45296 (path-to-regexp vulnerable to Regular Expression Denial of Service via multiple r)

High
Category
Supply Chain
Confidence
98% confidence
Finding
The lockfile includes path-to-regexp 0.1.12 via Express routing, and this version is associated with a Regular Expression Denial of Service issue. Because this skill provides a voice interface and likely runs an HTTP service with attacker-reachable routes, a crafted request path could consume excessive CPU and disrupt the service.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The README promotes always-on or wake-word-triggered microphone use and describes sending captured speech to backend services for STT/LLM/TTS processing, but it does not clearly warn users what audio/text is transmitted, retained, or exposed to third-party/local services. In a voice assistant context, this omission can lead to unintentional collection of sensitive spoken data and weak informed consent, especially because the project emphasizes zero-config ease of use.

Lp3

Medium
Category
MCP Least Privilege
Confidence
87% confidence
Finding
The skill documentation describes capabilities that access environment/configuration data and make network requests, but it does not declare any explicit tool scope such as permissions or allowed-tools. This creates an authorization gap where operators may not realize the skill can read local configuration like ~/.openclaw/openclaw.json and communicate with local/remote services, increasing the chance of over-privileged deployment.

Natural-Language Policy Violations

Medium
Confidence
79% confidence
Finding
The component table states `VOICEVOX` is used for `日本語音声合成`, which indicates a fixed Japanese output locale. Under SQP-3, forcing a specific language or locale without user opt-in or clear justification is a natural-language policy concern, and this file does not describe any user language choice or region-specific limitation.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
Automatic discovery of a gateway token from a user config file is credential access behavior, and the documentation presents it as normal startup flow without warning or security considerations. In this skill context, the server mediates requests to the OpenClaw gateway, so silent token ingestion increases the risk of unauthorized use, accidental privilege expansion, or insecure deployment assumptions.

Intent-Code Divergence

Medium
Confidence
96% confidence
Finding
The document explicitly states there are no secrets while also documenting automatic use of a gateway token from ~/.openclaw/openclaw.json. That contradiction can mislead operators and reviewers into underestimating credential handling, causing the token to be accessed, exposed, or logged without appropriate safeguards.

Natural-Language Policy Violations

Medium
Confidence
96% confidence
Finding
The document declares `lang="ja"`, and the interface strings and default speech-recognition language are fixed to Japanese (`ja-JP`) without any visible opt-in or language selection. The stated policy flags language or locale constraints when a skill forces a specific language without user choice or explicit justification.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The page sends recognized speech-derived commands and accumulated conversation history to backend endpoints (`/api/chat-stream` and `/api/tts`) but the UI does not clearly disclose that spoken content will be transmitted off the browser/session. In a voice-assistant context, users may reasonably assume local handling, so the lack of notice and consent can cause unintended exposure of sensitive spoken data.

Natural-Language Policy Violations

Medium
Confidence
95% confidence
Finding
The server hard-codes Japanese wake words and defaults speech recognition to `ja-JP`, which imposes a specific language/locale on users. The file does not present this as an explicit opt-in or offer an in-file language choice, so it conflicts with the policy against forcing a locale without user consent.

Natural-Language Policy Violations

Low
Confidence
87% confidence
Finding
The configuration table specifies `STT_LANG` with a default of `ja-JP`, which indicates a language/locale assumption. Under the policy rule, forcing a specific language is only acceptable when the user is offered a choice or the locale restriction is clearly documented and justified; this file does not provide that justification or explicit opt-in.

Natural-Language Policy Violations

Low
Confidence
78% confidence
Finding
This markdown file is written entirely in Japanese and does not indicate that language selection is optional or limited to a Japan-specific audience. Under the policy rule for language/locale constraints, a skill artifact that effectively mandates one language without opt-in can be considered a natural-language policy violation.

Known Vulnerable Dependency: body-parser==1.20.4 — 1 advisory(ies): CVE-2026-12590 (body-parser vulnerable to denial of service when invalid limit value silently di)

Low
Category
Supply Chain
Confidence
86% confidence
Finding
The lockfile pins body-parser 1.20.4, which is reported vulnerable to denial of service when invalid limit handling is triggered. In this file it is a transitive dependency of Express, so the issue is not evidence of malicious behavior, but it can still affect availability if the application exposes request parsing to untrusted clients.

Known Vulnerable Dependency: qs==6.14.2 — 3 advisory(ies): CVE-2026-82417 (qs: Denial of Service via Attacker Controlled isBuffer); CVE-2026-8723 (qs has a remotely triggerable DoS: qs.stringify crashes with TypeError on null/u); CVE-2026-82562 (qs array-limit bypass via bracket-key comma parsing)

Low
Category
Supply Chain
Confidence
80% confidence
Finding
The project locks qs 6.14.2, which has multiple reported denial-of-service and parsing-bypass advisories. Since qs is commonly used for parsing attacker-controlled query strings in Express applications, this can affect availability or input handling correctness if the service accepts untrusted HTTP requests.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"start": "node src/server.js"
  },
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.19.2"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "dotenv": "^16.4.5",
    "express": "^4.19.2"
  }
}
Confidence
40% confidence
Finding
Dependencies lack version pinning, allowing potential malicious package updates. Consider pinning versions.

Static analysis

Detected: suspicious.env_credential_access

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
src/server.js:37