T05 · Unauthorized Access and Privilege Escalation
- 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
Security audit
Security checks for vulnerabilities and agentic risk
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.
assets/app/webapp/server.py:107Unauthenticated WebSocket Endpoints Permit Remote Microphone Activation and Agent Invocation
assets/app/webapp/server.py:25Unauthenticated Clients Can Read Voice Transcripts and Agent Replies
assets/app/requirements.txt:1Unpinned Third-Party Dependencies Create a Non-Reproducible Supply-Chain Risk
assets/app/maylo_assistant/core.py:385Raw Voice Recordings Are Retained Indefinitely Without Explicit Access Controls
Package name closely resembles a popular package, suggesting possible typosquatting. Attackers publish malicious packages with similar names to trick developers into installing them.
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.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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})")
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.
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.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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:
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
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:
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.
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.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
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)
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.
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.
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.
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.
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.
# 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.
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.
# 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.
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.
# 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.
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.
# 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.
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.
# 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.
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.
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.
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.
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.
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# 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
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.
No suspicious patterns detected.