Back to skill

Security audit

Maylo Voice Assistant

Security checks for vulnerabilities and agentic risk

Overview

This voice assistant is purpose-aligned, but its default network web UI exposes microphone, transcript, and local-agent actions without authentication.

Install only if you are comfortable running a local voice assistant that records audio, stores WAV files and transcripts locally, and exposes unauthenticated WebSocket controls when bound to 0.0.0.0. Prefer binding the web UI to 127.0.0.1 or a trusted private tunnel, add authentication before LAN use, review retained recordings, and restrict the OpenClaw agent permissions used by this assistant.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/app/webapp/server.py:107
Finding

Unauthenticated WebSocket Endpoints Permit Remote Microphone Activation and Agent Invocation

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Error
Location
assets/app/webapp/server.py:25
Finding

Unauthenticated Clients Can Read Voice Transcripts and Agent Replies

Content
View full analysis
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
assets/app/requirements.txt:1
Finding

Unpinned Third-Party Dependencies Create a Non-Reproducible Supply-Chain Risk

Content
View full analysis
=81 removed it. setuptools<81 openwakeword==0.6.0 sounddevice numpy onnxruntime webrtcvad faster-whisper # Web UI fastapi uvicorn[standard] ``` ```bash python3 -m venv venv source venv/bin/activate python -m pip install --upgrade pip pip install -r requirements.txt # Tools used for audio output switching (optional) if command -v brew >/dev/null 2>&1; then brew list switchaudio-osx >/dev/null 2>&1 || brew install switchaudio-osx || true fi ``` ### Technical Analysis Only `openwakeword` is pinned to an exact version. The other direct dependencies are unconstrained, while `setuptools<81` permits a broad range of versions. Transitive dependencies are also not locked, and no package hashes are specified. The installer additionally upgrades `pip` and installs an unversioned Homebrew formula. As a result, the software reviewed during the audit may differ from the software installed later. Python package installation can execute package build hooks, and installed packages are subsequently imported and executed by the application. No malicious package or typosquatted dependency was identified in the reviewed file. The finding concerns unsafe dependency resolution and the resulting exposure to upstream compromise or incompatible releases. ### Attack Path 1. A dependency or one of its transitive dependencies publishes a compromised or unexpectedly incompatible release. 2. A user runs `scripts/install.sh`. 3. `pip install -r requirements.txt` resolves the mutable latest version allowed by each unconstrained requirement. 4. Installation-time hooks or later imports execute code from the newly resolved package. 5. That code runs with the permissions of ...[truncated 502 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
assets/app/maylo_assistant/core.py:385
Finding

Raw Voice Recordings Are Retained Indefinitely Without Explicit Access Controls

Content
View full analysis
None: with wave.open(str(path), "wb") as wf: wf.setnchannels(1) wf.setsampwidth(2) # int16 wf.setframerate(sample_rate) wf.writeframes(pcm_int16.tobytes()) ``` ### Technical Analysis Every processed utterance is written to the persistent `recordings` directory. The code does not delete recordings after transcription, enforce a maximum retention period, cap storage use, encrypt files, or explicitly set file mode `0600`. Actual file permissions depend on the process umask and directory permissions. On systems with permissive defaults, other local users or processes may be able to read the recordings. Backups and synchronization tools may also copy them, extending their lifetime beyond the application's host. The documentation identifies the recordings directory primarily as a log location and does not clearly establish a raw-audio retention policy. ### Attack Path 1. A local or remote user submits speech through wak ...[truncated 772 chars]
Remediation
View remediation
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
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (37)

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.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding

The skill describes and instructs use of shell scripts that install software, copy application files, start long-running services, and expose an HTTPS web UI on 0.0.0.0, but it does not declare any explicit tool scope or permissions. This creates a trust and review gap: an agent may execute file, environment, and shell-capable actions without the user seeing a bounded permission model, increasing the risk of unexpected system changes or network exposure.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · assets/app/bridge/milo_responder_openclaw.py (reported line 50)May include surrounding context.

python
message,
        "--json",
    ]
    p = subprocess.run(cmd, capture_output=True, text=True)
    if p.returncode != 0:
        raise RuntimeError(p.stderr.strip() or p.stdout.strip() or f"openclaw agent failed ({p.returncode})")

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

This Python file contains natural-language strings shown to users in Turkish: the normal fallback response '(Cevap üretilemedi)' and the error response 'Bir hata oldu: ...'. The policy for this audit flags language/locale constraints across all file types when the skill forces a specific language without offering a user choice or documenting a justified locale restriction.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The script is presented as an offline/minimal local wake-word test, but it performs a runtime network fetch via download_models(). That creates a supply-chain and integrity risk because execution behavior depends on remote content at run time, and it can also violate user expectations in an offline-first voice-assistant context.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · assets/app/maylo_assistant/core.py (reported line 85)May include surrounding context.

python
text = (text or "").strip()
    if not text:
        return
    subprocess.run(["say", "-v", SAY_VOICE, text], check=False)


def write_wav(path: Path, pcm_int16: np.ndarray, sample_rate: int = SAMPLE_RATE) -> None:

Tainted flow: 'SAY_VOICE' from os.getenv (line 58, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · assets/app/maylo_assistant/core.py (reported line 85)May include surrounding context.

python
text = (text or "").strip()
    if not text:
        return
    subprocess.run(["say", "-v", SAY_VOICE, text], check=False)


def write_wav(path: Path, pcm_int16: np.ndarray, sample_rate: int = SAMPLE_RATE) -> None:

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

Transcribed speech is written in plaintext to a bridge request file for another component without any visible user disclosure, integrity protection, or permission hardening. In this skill's context, spoken content may include sensitive requests, so persisting it to a predictable file path creates an avoidable local disclosure surface and potential cross-component leakage.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The request payload forces lang to tr, and elsewhere the transcription and spoken prompts are also fixed to Turkish. This is a natural-language policy concern because the skill constrains language/locale behavior without an explicit user choice or documented opt-in in this file.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · assets/app/maylo_assistant/core.py (reported line 376)May include surrounding context.

python
elif feedback == "beep":
                        # short system beep (usually less likely to be transcribed)
                        try:
                            subprocess.run(["/usr/bin/afplay", "/System/Library/Sounds/Ping.aiff"], check=False)
                        except Exception:
                            pass
                        time.sleep(0.2)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The code persistently saves microphone recordings and emits transcript state without any consent, retention policy, or access control shown in this path. In a voice assistant context, captured speech may contain sensitive personal or operational information, so silent disk persistence increases privacy and disclosure risk if local files are accessed by other users, processes, backups, or logs.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The Whisper transcription call pins language="tr", which prevents locale choice and may conflict with organizational language-choice policy. The file does not show any opt-in or justification that this is a region-specific Turkish-only tool.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The docstring says this path is 'Push-to-talk (server microphone): record -> ASR -> bridge -> say', which implies recording starts before speech synthesis. In reality, the function emits 'speaking', calls say("Dinliyorum"), and only then records from self.stream with pre-roll from recent local microphone blocks, so the documented order and behavior contradict the implementation.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

The script uses pkill to stop prior Python processes, which is a potentially disruptive operation affecting running services. Although there is a brief code comment, there is no user-facing prompt or warning that existing assistant components will be forcibly terminated before startup.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · assets/app/run_macmini.sh (reported line 17)May include surrounding context.

sh
# Start OpenClaw responder (real Milo, embedded local)
export PYTHONUNBUFFERED=1

nohup python -u "$ROOT/bridge/milo_responder_openclaw.py" >"$ROOT/recordings/responder.log" 2>&1 &

# Start full assistant (wake word + record + whisper + say)
# Optional: set MAYLO_INPUT_DEVICE to force a specific input.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · assets/app/run_macmini.sh (reported line 29)May include surrounding context.

sh
# Start OpenClaw responder (real Milo, embedded local)
export PYTHONUNBUFFERED=1

nohup python -u "$ROOT/bridge/milo_responder_openclaw.py" >"$ROOT/recordings/responder.log" 2>&1 &

# Start full assistant (wake word + record + whisper + say)
# Optional: set MAYLO_INPUT_DEVICE to force a specific input.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/run_assistant.sh (reported line 26)May include surrounding context.

sh
# Start OpenClaw responder (real Milo, embedded local)
export PYTHONUNBUFFERED=1

nohup python -u "$ROOT/bridge/milo_responder_openclaw.py" >"$ROOT/recordings/responder.log" 2>&1 &

# Start full assistant (wake word + record + whisper + say)
# Optional: set MAYLO_INPUT_DEVICE to force a specific input.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/run_assistant.sh (reported line 27)May include surrounding context.

sh
# Start OpenClaw responder (real Milo, embedded local)
export PYTHONUNBUFFERED=1

nohup python -u "$ROOT/bridge/milo_responder_openclaw.py" >"$ROOT/recordings/responder.log" 2>&1 &

# Start full assistant (wake word + record + whisper + say)
# Optional: set MAYLO_INPUT_DEVICE to force a specific input.

Session Persistence

Medium
Category
Rogue Agent
Confidence
65% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · scripts/run_web_https.sh (reported line 37)May include surrounding context.

sh
# Start OpenClaw responder (real Milo, embedded local)
export PYTHONUNBUFFERED=1

nohup python -u "$ROOT/bridge/milo_responder_openclaw.py" >"$ROOT/recordings/responder.log" 2>&1 &

# Start full assistant (wake word + record + whisper + say)
# Optional: set MAYLO_INPUT_DEVICE to force a specific input.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

The server keeps a global client list and broadcasts assistant state updates, including last_transcript and last_reply, to every connected /ws client. In a voice assistant context, those fields can contain sensitive spoken content, so any connected client can observe another user's transcripts and responses without authentication, authorization, or per-session isolation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The hard-coded phrase Dinliyorum makes the skill speak in Turkish regardless of user preference. The file does not offer opt-in language selection or explain that this assistant is intentionally Turkish-only, which conflicts with the language/locale policy criteria.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

This code captures live microphone input and transmits PCM audio to /ws/audio, but the interface only says 'hold to talk' and 'Konuş' rather than explicitly warning that speech audio will be sent to the server. For code files, network transmission of user data should have some visible disclosure; none is present here beyond implicit behavior.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The script uses pkill to stop previously running Python processes matching specific patterns, which is a destructive operation affecting running processes. There is no preceding user-facing warning, confirmation prompt, or explanatory comment indicating that existing assistant instances will be killed before startup.

Content

No source excerpt is available for this finding.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
80% confidence
Finding

Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Content

Scanner excerpt · scripts/run_web_https.sh (reported line 31)May include surrounding context.

sh
# Generate a self-signed cert if missing
if [[ ! -f cert.pem || ! -f key.pem ]]; then
  openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj "/CN=maylo.local" >/dev/null 2>&1 || true
  chmod 600 key.pem cert.pem || true
fi

# Kill previous uvicorn

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

Line L35 forcefully kills any process matching "uvicorn webapp.server:app", which is a potentially disruptive operation affecting running services. Although there is an internal comment, there is no user-facing disclosure before termination beyond the final success message, so the destructive action may occur unexpectedly.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.