Back to skill

Security audit

Clawface

Security checks for vulnerabilities and agentic risk

Overview

The skill appears to run the advertised avatar chat UI, but the server it starts can use OpenClaw credentials with broad authority and is not sufficiently locked down.

Review before installing. Only run this on a trusted machine and private network, prefer loopback-only binding, use the least-privileged gateway token possible, and verify or pin the downloaded native TTS runtime before use. Stop the node process when finished.

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

T05 · Unauthorized Access and Privilege Escalation

Error
Location
bin/serve.js:190
Finding
Unauthenticated WebSocket Proxy Grants Privileged Gateway Access<![CDATA[ ## Vulnerability Details **File Location**: `bin/serve.js:190-226`, `bin/serve.js:302-323`, `bin/serve.js:337-358`, `bin/serve.js:563-573` **Vulnerability Type**: Missing client authentication and authorization on a privileged WebSocket proxy **Risk Level**: High ### Vulnerable Code ```js function buildConnectFrame(nonce) { const signedAtMs = Date.now(); const scopes = [ "operator.admin", "operator.read", "operator.write", "operator.approvals", "operator.pairing", ]; const payload = buildV3Payload({ deviceId, clientId: "cli", clientMode: "cli", role: "operator", scopes, signedAtMs, token: gatewayToken, nonce, platform: "node", }); const signature = signPayload(privateKeyPem, payload); return JSON.stringify({ type: "req", id: `${Date.now().toString(16)}-proxy`, method: "connect", params: { minProtocol: 3, maxProtocol: 3, client: { id: "cli", displayName: "Clawface Web", version: "1.0", platform: "node", mode: "cli", instanceId: `clawface-${Date.now()}`, }, locale: "en-US", userAgent: "clawface-web", role: "operator", scopes, caps: [], device: { id: deviceId, publicKey: publicKeyBase64Url, signature, signedAt: signedAtMs, nonce, }, auth: { token: gatewayToken }, }, }); } ``` ```js // Browser → gateway browserSocket.on("data", (chunk) => { brBuf = Buffer.concat([brBuf, chunk]); while (true) { const frame = decodeFrame(brBuf); if (!frame) break; brBuf = brBuf.subarray(frame.totalLen); if (frame.opcode === 0x08) { sendClose(gwSocket, true); gwSocket.end(); browserSocket.end(); return; } if (frame.opcode === 0x09) { sendPong(browserSocket, frame.payload, false); continue; } if (frame.opcode !== 0x01) continue; // Forward browser frames to gateway (must be masked per RFC 6455) sendText(gwSocket, frame.payload.toString("utf8"), true); ...[truncated 3432 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the HTTP service explicitly to a loopback address: ```js server.listen(port, "127.0.0.1", () => { console.log(`http://127.0.0.1:${port}`); }); ``` 2. Generate a cryptographically random, per-launch session token and require it during the WebSocket handshake. 3. Validate `Origin` against an explicit allowlist and reject missing or unexpected origins. 4. Validate `Host` and reject requests addressed through unapproved hostnames. 5. Do not provide a transparent arbitrary-message proxy. Parse browser messages and allowlist only the gateway methods needed by the avatar interface. 6. Request only the minimum gateway scopes necessary for chat functionality. Remove administrative, approval, and pairing scopes unless a documented feature strictly requires them. 7. Enforce message-size, connection-count, rate, and idle-time limits. 8. Return an error and close the connection if gateway credentials are incomplete or upstream authentication fails. 9. Consider using a narrowly scoped server-side API instead of exposing the gateway protocol directly to browser clients. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
bin/serve.js:363
Finding
Unauthenticated and Unbounded TTS Endpoint Enables Resource Exhaustion<![CDATA[ ## Vulnerability Details **File Location**: `bin/serve.js:363-367`, `bin/serve.js:414-436`, `bin/serve.js:477-479`, `bin/serve.js:571-573` **Vulnerability Type**: Unbounded request buffering and unrestricted native process creation **Risk Level**: Medium ### Vulnerable Code ```js function handleTts(req, res) { let body = ""; req.on("data", (chunk) => (body += chunk)); req.on("end", () => { let text; try { text = JSON.parse(body).text || ""; } catch { res.writeHead(400); res.end("Bad request"); return; } ``` ```js execFile( bin, [ `--vits-model=${modelFile}`, `--vits-tokens=${tokensFile}`, `--vits-data-dir=${dataDir}`, `--output-filename=${outputPath}`, text, ], { env, timeout: 30000 }, (err) => { if (err) { res.writeHead(500); res.end(`TTS failed: ${err.message}`); try { fs.rmSync(tmpDir, { recursive: true }); } catch {} return; } try { const audio = fs.readFileSync(outputPath); res.writeHead(200, { ...securityHeaders, "Content-Type": "audio/wav", "Content-Length": audio.length, }); res.end(audio); } finally { try { fs.rmSync(tmpDir, { recursive: true }); } catch {} } } ); ``` ```js if (urlPath === "/tts" && req.method === "POST") { return handleTts(req, res); } ``` ```js server.listen(port, () => { console.log(`http://localhost:${port}`); }); ``` ### Technical Analysis The `/tts` endpoint has no authentication or authorization check. Incoming request bodies are appended to a JavaScript string until the client finishes sending data, with no request-size or text-length limit. A sufficiently large request can consequently consume excessive process memory. Every valid request creates a temporary directory and launches the native `sherpa-onnx-offline-tts` executable. There is no concurrency cap, queue limit, or rate limit. Multiple simultaneous requests can therefore con ...[truncated 1527 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Bind the server explicitly to `127.0.0.1`. 2. Require the same random per-launch session credential used to protect the browser session. 3. Enforce a small maximum request-body size and immediately destroy or drain connections that exceed it. 4. Limit accepted TTS text to a documented maximum character or byte count. 5. Validate `Content-Type` and accept only `application/json`. 6. Add a bounded TTS job queue and a strict concurrency limit. 7. Apply per-client and global request-rate limits. 8. Set request, body-read, subprocess, and response timeouts. 9. Handle request abortion by terminating associated subprocesses and removing temporary directories. 10. Run the TTS process with restricted filesystem permissions and under a constrained operating-system account or sandbox where practical. ]]>

T08 · Insecure Dependencies

Warning
Location
SKILL.md:13
Finding
Downloaded Native Runtime Is Not Protected by Integrity Verification<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:13-45` **Vulnerability Type**: Unverified third-party executable and model downloads **Risk Level**: Medium ### Vulnerable Code ```yaml "install": [ { "id": "download-runtime-macos", "kind": "download", "os": ["darwin"], "url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-osx-universal2-shared.tar.bz2", "archive": "tar.bz2", "extract": true, "stripComponents": 1, "targetDir": "runtime", "label": "Download sherpa-onnx runtime (macOS)", }, { "id": "download-runtime-linux-x64", "kind": "download", "os": ["linux"], "url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/v1.12.23/sherpa-onnx-v1.12.23-linux-x64-shared.tar.bz2", "archive": "tar.bz2", "extract": true, "stripComponents": 1, "targetDir": "runtime", "label": "Download sherpa-onnx runtime (Linux x64)", }, { "id": "download-model-lessac", "kind": "download", "url": "https://github.com/k2-fsa/sherpa-onnx/releases/download/tts-models/vits-piper-en_US-lessac-high.tar.bz2", "archive": "tar.bz2", "extract": true, "targetDir": "models", "label": "Download Piper en_US lessac (high) voice model", }, ], ``` ### Technical Analysis The installation metadata downloads and extracts prebuilt native runtime archives and a model archive without declaring an expected cryptographic digest or signature. HTTPS protects data in transit under normal conditions, and the runtime URLs refer to a named GitHub repository and pinned version. However, neither HTTPS nor a versioned filename cryptographically binds the installation to the exact artifact that was reviewed. If a release asset, repository account, redirect destination, or distribution path is compromised, the installer has no independent mechanism to detect altered archive contents. ...[truncated 1407 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Declare a trusted SHA-256 or stronger digest for every downloaded archive and fail installation if verification fails. 2. Pin model artifacts by digest as well as by URL. 3. Prefer upstream artifacts with verifiable release signatures or build provenance, and verify those attestations before extraction. 4. Record hashes in reviewed, version-controlled Skill metadata rather than retrieving expected hashes from the same location as the archives. 5. Validate archive entries before extraction to reject absolute paths, traversal entries, unsafe symbolic links, and unexpected executable files. 6. Restrict executable permissions to the specific runtime files that require them. 7. Document the upstream source, release version, artifact hashes, and update process. 8. Review and update pinned artifacts deliberately rather than automatically following mutable tags or release assets. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (15)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This finding reflects that the browser UI is not purely local display logic; it exchanges chat data with a backend via WebSocket and triggers TTS endpoints, which is materially broader than a simple static local page. Even if expected for a chat avatar, under-describing this behavior can mislead users about data flow and privacy exposure.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding
This finding reflects that the browser UI is not purely local display logic; it exchanges chat data with a backend via WebSocket and triggers TTS endpoints, which is materially broader than a simple static local page. Even if expected for a chat avatar, under-describing this behavior can mislead users about data flow and privacy exposure.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding
This finding reflects that the browser UI is not purely local display logic; it exchanges chat data with a backend via WebSocket and triggers TTS endpoints, which is materially broader than a simple static local page. Even if expected for a chat avatar, under-describing this behavior can mislead users about data flow and privacy exposure.

Description-Behavior Mismatch

High
Confidence
95% confidence
Finding
The skill is presented as a local web UI server, but it also upgrades browser WebSocket traffic and proxies it to a remote gateway while performing authenticated login using local device credentials and a gateway token. This expands the trust boundary substantially: any page that can reach the local port can potentially drive a privileged remote session through the user's machine, and the behavior is not clearly disclosed by the stated skill description.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill explicitly performs networked behavior: it downloads binaries/models during install and, at runtime, connects to an upstream gateway over WebSocket while serving a local HTTP interface. Declaring no permissions or allowed tool scope obscures these capabilities from reviewers and users, increasing the risk of unintended credential use and network access.

Vague Triggers

Medium
Confidence
88% confidence
Finding
The description says only 'Start the Clawface 3D avatar web UI' without defining specific trigger phrases, scope constraints, or exclusion conditions. In a manifest/markdown file, this broad action-oriented wording can overlap with many generic requests involving avatars, chat UIs, or web interfaces, increasing the risk of unintended invocation.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The server reads device identity material from an identity file, derives a public key identifier, and later uses the private key for gateway authentication, but the user-facing behavior is not surfaced in the skill description or at runtime. Hidden handling of credentials increases the risk of users unknowingly exposing privileged identity material to a component they believe only serves static files.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The proxy constructs and sends authenticated connect frames containing a gateway token and device signature to a remote endpoint, yet this sensitive remote-auth behavior is not apparent from the skill's local-UI description. The mismatch in declared purpose makes the feature more dangerous because users may expose the service locally without realizing it brokers privileged remote access.

Description-Behavior Mismatch

Medium
Confidence
91% confidence
Finding
In addition to serving static content, the skill exposes a POST /tts endpoint that accepts arbitrary text and invokes local speech synthesis tooling. Even though execFile avoids shell injection, this still exposes local code execution functionality over HTTP and can be abused by any local webpage or process that can access the listening port, potentially causing unwanted resource consumption or misuse of bundled tooling.

Context-Inappropriate Capability

Medium
Confidence
88% confidence
Finding
The code launches an external binary to process attacker-controlled text from an HTTP request. While the arguments are passed safely without a shell, this still creates an execution surface in a skill whose advertised purpose is only serving a local UI; if the TTS binary or model stack has parsing flaws, the endpoint becomes a reachable trigger for local exploitation or denial of service.

Description-Behavior Mismatch

Medium
Confidence
98% confidence
Finding
The bundled web UI does substantially more than the declared purpose of serving a local 3D avatar page: it establishes remote WebSocket chat, fetches chat history, sends chat prompts, invokes a /tts endpoint, and plays returned audio. This hidden capability expansion is dangerous because users and reviewers may grant trust or network access based on an incomplete manifest, enabling unexpected data flows and remote interaction.

Context-Inappropriate Capability

Medium
Confidence
95% confidence
Finding
The UI posts user-visible text to a /tts endpoint, receives audio data, decodes it, and plays it automatically. That is an additional networked/media-processing capability not implied by merely launching a local avatar UI, and it creates an undisclosed path for transmitting conversation content to a backend service.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The fetch('/tts', {body: JSON.stringify({text:a})}) request sends user text to a service without any visible consent or disclosure in this file. Because the skill is presented as a local avatar UI, this undisclosed transmission is more dangerous: users may reasonably assume their typed content stays local when it does not.

Context-Inappropriate Capability

Medium
Confidence
97% confidence
Finding
The code creates a WebSocket using a configurable wsUrl, sends chat.history and chat.send requests, and streams assistant responses, which is materially beyond 'start a local avatar web UI.' In this context, undisclosed remote communication increases risk of silent exfiltration of user prompts and server-driven content reaching the browser under the guise of a local-only tool.

Natural-Language Policy Violations

Low
Confidence
91% confidence
Finding
The HTML root sets `lang="en"`, which is a natural-language locale choice applied by default to the skill UI. Under the policy, forcing a specific language or locale without user opt-in can be a violation when no alternative or justification is provided.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
bin/serve.js:465

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
dist/thermion_dart.js:1