Back to skill

Security audit

Voice Assistant

Security checks for vulnerabilities and agentic risk

Overview

This looks like a real voice assistant, but it needs Review because it handles live speech and credentials with weak privacy and endpoint safeguards.

Install only if you are comfortable with an always-on local voice assistant. Keep the gateway on the default local address unless you have a trusted secure endpoint, treat the .env tokens as secrets, avoid speaking sensitive information while default logging is enabled, and prefer pinned dependencies or an isolated environment before running it.

Vulnerability Patterns
  • 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
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T08 · Insecure Dependencies

Warning
Location
scripts/requirements.txt:1
Finding
Unpinned Third-Party Dependencies Allow Unreviewed Package Code<![CDATA[ ## Vulnerability Details **File Location**: `scripts/requirements.txt:1-11`; installation command at `SKILL.md:49-51` **Vulnerability Type**: Unpinned and unhashed third-party dependencies **Risk Level**: Medium ### Vulnerable Code ```text pvporcupine>=3.0 faster-whisper>=1.0 elevenlabs>=2.0 av sounddevice numpy websockets>=12.0 pystray>=0.19 Pillow pynput>=1.7 python-dotenv>=1.0 ``` The documented installation procedure executes these dependency declarations: ```bash python -m venv venv venv\Scripts\pip install -r requirements.txt ``` ### Technical Analysis Every declared dependency is either completely unpinned or constrained only by a minimum version. No lock file, package hashes, index restrictions, or integrity-verification mechanism is provided. Consequently, the code installed by the documented command can change without any modification to the audited project. A future release satisfying one of the version constraints will be accepted automatically. Packages such as `sounddevice`, `pynput`, `pystray`, and the speech-service SDKs execute native or Python code with the current user's privileges. This is a supply-chain weakness rather than evidence that any currently declared package is malicious. ### Attack Path 1. An attacker compromises a maintainer account, package release process, dependency distribution infrastructure, or the package index configured on the user's system. 2. The attacker publishes a malicious version satisfying the open-ended constraint, such as a version greater than or equal to the declared minimum. 3. A user follows the documented setup procedure and runs `pip install -r requirements.txt`. 4. Pip resolves the malicious release because no exact version or trusted hash is required. 5. Malicious package code executes during installation or when imported by the assistant. 6. The package inherits the privileges and data access of the user running the application. ### Impact Assessment Successful exploitation could ...[truncated 626 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct dependency to an exact reviewed version, for example: ```text websockets==<reviewed-version> python-dotenv==<reviewed-version> ``` 2. Generate a fully resolved lock file that includes transitive dependencies. 3. Record cryptographic hashes and install with hash enforcement: ```bash python -m pip install --require-hashes -r requirements.lock ``` 4. Use a trusted, explicitly configured package index and disable unintended extra indexes. 5. Review package provenance, maintainers, release history, and native binary distribution before approving updates. 6. Run dependency vulnerability and malware scans in CI. 7. Update dependencies through a controlled process that reviews code and release changes before regenerating hashes. 8. Install and run the application as a non-administrative user in an isolated virtual environment. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/src/gateway_client.py:27
Finding
Gateway Credentials and Transcripts Can Be Sent over Insecure or Attacker-Controlled WebSocket Endpoints<![CDATA[ ## Vulnerability Details **File Location**: `scripts/src/config.py:12-13`; `scripts/src/gateway_client.py:27-59` **Vulnerability Type**: Missing transport-security and destination validation for sensitive WebSocket traffic **Risk Level**: Medium ### Vulnerable Code Configuration accepts an unrestricted gateway URL and defaults to plaintext WebSocket transport: ```python # OpenClaw Gateway GATEWAY_URL = os.getenv("GATEWAY_URL", "ws://127.0.0.1:18789") GATEWAY_TOKEN = os.getenv("GATEWAY_TOKEN", "") ``` The configured endpoint is used directly, after which the bearer token is transmitted: ```python async def connect(self): """Establish WebSocket connection and authenticate.""" log.info("Connecting to gateway at %s", GATEWAY_URL) self.ws = await websockets.connect(GATEWAY_URL, max_size=2**24) # Step 1: Receive connect.challenge raw = await self.ws.recv() challenge = json.loads(raw) if challenge.get("event") != "connect.challenge": raise ConnectionError(f"Expected connect.challenge, got: {challenge}") log.debug("Received challenge: %s", challenge["payload"].get("nonce")) # Step 2: Send connect request connect_id = str(uuid.uuid4()) await self.ws.send(json.dumps({ "type": "req", "id": connect_id, "method": "connect", "params": { "minProtocol": 3, "maxProtocol": 3, "client": { "id": "node-host", "version": "1.0.0", "platform": "windows", "mode": "node" }, "role": "operator", "scopes": ["operator.write"], "caps": [], "auth": { "token": GATEWAY_TOKEN } } })) ``` User transcripts are subsequently sent through the same connection: ```python await self.ws.send(json.dumps({ "type": "req", "id": req_id, "method": "chat.send", "params": { "sessionKey": "ma ...[truncated 2494 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse `GATEWAY_URL` before connecting. 2. Allow `ws://` only when the resolved destination is strictly loopback, such as `127.0.0.1`, `::1`, or a separately validated local socket. 3. Require `wss://` for every non-loopback endpoint. 4. Reject malformed URLs, embedded credentials, unexpected schemes, redirects, and unapproved ports. 5. Support an explicit allowlist of trusted gateway hostnames or certificate identities. 6. Preserve normal TLS certificate and hostname validation; consider certificate pinning for managed deployments. 7. Display a prominent warning and require explicit confirmation before sending a token to a newly configured remote endpoint. 8. Use narrowly scoped, short-lived gateway tokens and provide rotation and revocation procedures. 9. Avoid relying solely on a syntactic challenge. Where supported, use a nonce-bound authentication exchange so the raw bearer token is not directly exposed. 10. Add automated tests confirming that remote `ws://` URLs and untrusted destinations are rejected. ]]>

T09 · Insecure Skill Coding Practices

Note
Location
scripts/src/assistant.py:94
Finding
Sensitive Conversation Content Is Logged at INFO Level<![CDATA[ ## Vulnerability Details **File Location**: `scripts/src/assistant.py:94-115`; `scripts/src/audio_pipeline.py:287-294` **Vulnerability Type**: Sensitive information exposure through application logs **Risk Level**: Low ### Vulnerable Code The main orchestration code logs the complete user transcript and the first 200 characters of the assistant response: ```python log.info("You said: %s", text) # Play thinking sound (mic suppressed during playback, then unsuppressed) play_thinking() # Send to gateway and speak response if _loop: asyncio.run_coroutine_threadsafe(_send_and_speak(text), _loop) async def _send_and_speak(text: str): """Send text to gateway, collect response, speak it, then listen for follow-up.""" global _in_conversation try: full_response = "" async for chunk in _gateway.send_message(text): full_response = chunk # Stop thinking sound stop_thinking() if full_response: log.info("Assistant: %s", full_response[:200]) ``` The transcription function independently logs the complete transcript a second time: ```python def transcribe(audio: np.ndarray) -> str: """Transcribe int16 audio array to text using faster-whisper.""" model = _get_whisper() audio_float = audio.astype(np.float32) / 32768.0 segments, info = model.transcribe(audio_float, beam_size=1, language="en") text = " ".join(seg.text.strip() for seg in segments).strip() log.info("Transcribed (%s): %s", f"{info.duration:.1f}s", text) return text ``` ### Technical Analysis Conversation content is emitted at `INFO` level, which is enabled by default in `assistant.py`. The user transcript is logged twice, and assistant output is partially logged. Although the current logging configuration writes to the process output rather than explicitly creating a file, console output may be retained by: - Terminal capture or redirection. - Process supervisors. - Debugging and support b ...[truncated 1524 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Remove conversation text from default INFO-level logging. 2. Replace content logs with non-sensitive operational metadata, for example: ```python log.info("Transcription completed in %.1fs", info.duration) log.info("Assistant response received (%d characters)", len(full_response)) ``` 3. If content logging is needed for troubleshooting, require an explicit opt-in diagnostic setting. 4. Emit diagnostic content only at DEBUG level and clearly warn users before enabling it. 5. Apply redaction for credentials, tokens, email addresses, payment data, and other common sensitive patterns. 6. Prevent duplicate transcript logging by keeping a single metadata-only transcription event. 7. Configure downstream logging with restrictive access controls, short retention periods, and encryption where logs are persisted. 8. Document what data is logged and provide users with a method to disable and delete diagnostic records. 9. Add tests that verify transcript and response strings do not appear in default logs. ]]>
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
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (33)

YARA rule 'keylogger_indicators': Keylogger functionality in scripts or source code [malware]

High
Category
YARA Match
Content
rget=_speak_then_listen, daemon=True).start()
        else:
            _in_conversation = False
            log.warning("Empty response from gateway")

    except Exception as e:
        stop_thinking()
        _in_conversation = False
        log.error("Error communicating with gateway: %s", e)


def _setup_hotkey():
    """Register global hotkey as alternative to wake word."""
    try:
        from pynput.keyboard import GlobalHotKeys

        parts = config.HOTKEY.split("+")
        combo_parts = []
        for p in parts:
            p = p.strip().lower()
            if p in ("ctrl", "control"):
                combo_parts.append("<ctrl>")
            elif p in ("shift",):
                combo_parts.append("<shift>")
            elif p in ("alt",):
                combo_parts.append("<alt>")
            elif p in ("cmd", "win", "super"):
                combo_parts.append("<cmd>")
            else:
                combo_parts.append(p)
        combo = "+".join(combo_parts)
Confidence
70% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Credential Access

High
Category
Privilege Escalation
Content
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
"""Configuration loader — reads settings from .env file."""

import os
from pathlib import Path
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
# Load .env from project root
_project_root = Path(__file__).resolve().parent.parent
load_dotenv(_project_root / ".env")

# OpenClaw Gateway
GATEWAY_URL = os.getenv("GATEWAY_URL", "ws://127.0.0.1:18789")
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
# Load .env from project root
_project_root = Path(__file__).resolve().parent.parent
load_dotenv(_project_root / ".env")

# OpenClaw Gateway
GATEWAY_URL = os.getenv("GATEWAY_URL", "ws://127.0.0.1:18789")
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
# Load .env from project root
_project_root = Path(__file__).resolve().parent.parent
load_dotenv(_project_root / ".env")

# OpenClaw Gateway
GATEWAY_URL = os.getenv("GATEWAY_URL", "ws://127.0.0.1:18789")
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
# Load .env from project root
_project_root = Path(__file__).resolve().parent.parent
load_dotenv(_project_root / ".env")

# OpenClaw Gateway
GATEWAY_URL = os.getenv("GATEWAY_URL", "ws://127.0.0.1:18789")
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
88% confidence
Finding
The skill documents access to environment secrets and local file creation/modification (.env creation, venv/dependency installation) but does not declare any explicit tool scope or permissions metadata. This creates a transparency and policy gap: users and hosting platforms cannot clearly evaluate or constrain what the skill needs before use, increasing the chance of over-privileged execution or unsafe deployment.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The skill description and quick-start flow emphasize voice convenience but do not prominently warn that it continuously listens for a wake word and automatically performs follow-up recording after responses. This is a privacy-relevant omission because users may deploy always-on microphone software without fully understanding that ambient speech can be captured beyond a single push-to-talk interaction.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The architecture explicitly describes a microphone that is continuously available for wake-word monitoring, records follow-up speech, transcribes it, and forwards text to another component, but it does not mention any user-facing consent, recording indicator, retention policy, or privacy notice. In a voice-assistant skill, this omission increases the risk of covert or unexpected capture of nearby speech and can lead to privacy harm even if the implementation is intended for local use.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The assistant sends transcribed microphone input to a gateway service without any explicit consent notice, transmission indicator, or user-facing privacy warning in this code path. Because voice input may contain sensitive personal or credential information, silent forwarding increases privacy and data exposure risk even if the gateway is expected functionality.

Natural-Language Policy Violations

Medium
Confidence
97% confidence
Finding
The transcription call hard-codes `language="en"`, which enforces a specific language regardless of the user's actual locale or preference. This is a natural-language policy concern because the file provides no opt-in, fallback, or justification for restricting recognition to English.

Missing User Warnings

Medium
Confidence
91% confidence
Finding
This code sends a credential-bearing authentication payload over the network by placing GATEWAY_TOKEN into the WebSocket connect request. Although the code logs the connection target, it does not provide any user-facing warning, confirmation, or explanatory comment/docstring disclosing that a sensitive token will be transmitted.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code sends arbitrary spoken-response text to ElevenLabs, a third-party cloud TTS provider, without any in-code consent flow, disclosure, or data-minimization guardrails. In a voice assistant context, responses may contain sensitive user data, making this a real privacy and data-exposure issue even if the feature is intentional.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pvporcupine>=3.0
faster-whisper>=1.0
elevenlabs>=2.0
av
Confidence
90% confidence
Finding
Using 'pvporcupine>=3.0' permits any future major/minor release above 3.0, which weakens reproducibility and increases supply-chain exposure if upstream publishes a bad release. There is no evidence of active exploitation here, but open-ended version ranges are a recognized dependency hygiene risk.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pvporcupine>=3.0
faster-whisper>=1.0
elevenlabs>=2.0
av
sounddevice
Confidence
90% confidence
Finding
Using 'faster-whisper>=1.0' leaves the installed version unconstrained above the minimum, so different users may get different code and security posture. For an audio-processing assistant, that can affect native/transitive components and make incident response harder.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pvporcupine>=3.0
faster-whisper>=1.0
elevenlabs>=2.0
av
sounddevice
numpy
Confidence
90% confidence
Finding
Using 'elevenlabs>=2.0' allows future upstream releases without review, which is a supply-chain and stability concern. While this is not direct proof of compromise, dependency drift can introduce vulnerable API clients or unexpected behavior in a network-connected voice assistant.

Unpinned Dependencies

Low
Category
Supply Chain
Content
pvporcupine>=3.0
faster-whisper>=1.0
elevenlabs>=2.0
av
sounddevice
numpy
websockets>=12.0
Confidence
93% confidence
Finding
The dependency 'av' is unpinned, so installations may resolve to different versions over time, reducing build reproducibility and increasing supply-chain risk if a newly published version is vulnerable or malicious. In a voice assistant context that processes media/audio, this matters because codec/parsing libraries are historically exposed to malformed-input issues.

Unpinned Dependencies

Low
Category
Supply Chain
Content
faster-whisper>=1.0
elevenlabs>=2.0
av
sounddevice
numpy
websockets>=12.0
pystray>=0.19
Confidence
93% confidence
Finding
The dependency 'sounddevice' is unpinned, allowing non-deterministic installs and making it hard to verify whether deployed environments use a secure version. Although this is usually operational rather than immediately exploitable, audio-device libraries can still inherit vulnerabilities through dependency drift.

Unpinned Dependencies

Low
Category
Supply Chain
Content
elevenlabs>=2.0
av
sounddevice
numpy
websockets>=12.0
pystray>=0.19
Pillow
Confidence
97% confidence
Finding
The dependency 'numpy' is not pinned to an exact version, so the installed package may vary across environments and may include versions with known advisories. This is more relevant here because the static analysis already indicates multiple historical advisories, but the manifest does not let reviewers determine whether affected versions are excluded.

Unverifiable Dependency: numpy has 16 known advisory(ies) (CVE-2014-1859 (Numpy arbitrary file write via symlink attack); CVE-2021-41495 (NumPy NULL Pointer Dereference); CVE-2021-33430 (NumPy Buffer Overflow (Disputed)) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
95% confidence
Finding
The manifest does not pin 'numpy', and the package has multiple known historical advisories, so reviewers cannot verify from this file whether deployed versions are affected or patched. This is a real supply-chain risk, though the manifest alone does not prove an exploitable vulnerable version is currently installed.

Static analysis

No suspicious patterns detected.