Back to skill

Security audit

Local Voice Agent

Security checks for vulnerabilities and agentic risk

Overview

This voice assistant skill is mostly purpose-aligned, but it overstates its privacy and completeness and includes unsafe configuration, network, and dependency practices that should be reviewed before use.

Install only if you are comfortable reviewing and hardening it first: remove the shell `eval`, keep the TTS service bound to localhost, avoid remote HTTP TTS unless trusted, pin downloaded dependencies/models, and treat microphone recordings, transcriptions, and cached speech as sensitive local data.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (4)

T09 · Insecure Skill Coding Practices

Error
Location
bin/voice-agent.sh:106
Finding
Configuration-Based Arbitrary Command Execution Through eval<![CDATA[ ## Vulnerability Details **File Location**: `bin/voice-agent.sh:106-110` **Vulnerability Type**: Shell command injection through unsafe configuration expansion **Risk Level**: High ### Vulnerable Code ```bash WHISPER_DIR=$(grep "whisper_dir:" "$CONFIG_FILE" | sed 's/.*: *//;s/["'\'']//g') if [ -z "$WHISPER_DIR" ]; then WHISPER_DIR="$HOME/.local/whisper.cpp" fi WHISPER_DIR=$(eval echo "$WHISPER_DIR") ``` The affected setting is explicitly presented as user-editable: ```yaml # config/voices.yaml:12-13 # Whisper.cpp installation path whisper_dir: ~/.local/whisper.cpp ``` ### Technical Analysis The script reads `whisper_dir` from `config/voices.yaml` and interpolates the resulting string into an `eval` command. Unlike normal variable expansion, `eval` reparses its arguments as shell syntax. Consequently, command substitutions, shell operators, redirections, and other shell constructs embedded in the YAML value are interpreted and executed. The use of `eval` is unnecessary for expanding a leading tilde. The Python STT implementation already performs safe home-directory expansion with `os.path.expanduser`, but the shell dependency check independently processes the same setting using unsafe shell evaluation. ### Attack Path 1. An attacker obtains the ability to modify the installed Skill configuration, such as through a compromised update, writable shared installation, malicious archive replacement, or another local integrity failure. 2. The attacker changes the setting to a value containing shell syntax, for example: ```yaml whisper_dir: '$(touch /tmp/voice-agent-eval-executed)' ``` 3. The victim starts `bin/voice-agent.sh` in any normal operating mode. 4. `check_dependencies` reads the malicious value. 5. `eval echo "$WHISPER_DIR"` reparses and executes the command substitution. 6. A real payload would execute with all privileges and filesystem access held by the user running the Skill. ### Impact Assessment Successful exploitati ...[truncated 445 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Remove `eval` completely. - Parse YAML with `yaml.safe_load` rather than `grep` and `sed`. - Expand only a documented leading `~/` prefix without evaluating shell syntax. - Validate that the resulting value is a string and resolves to an expected directory. - Consider resolving and constraining the path with `realpath` before use. - Ensure installed configuration files are writable only by the owning user. A safe shell approach for the documented use case is: ```bash case "$WHISPER_DIR" in "~") WHISPER_DIR="$HOME" ;; "~/"*) WHISPER_DIR="$HOME/${WHISPER_DIR#~/}" ;; esac ``` Preferably, use the same Python YAML-loading implementation as `lib/stt.py` so that shell parsing is not required. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
bin/voice-agent.sh:162
Finding
Predictable Audio Files in an Unverified Shared Temporary Directory<![CDATA[ ## Vulnerability Details **File Location**: `bin/voice-agent.sh:28, 82, 162-169` **Vulnerability Type**: Unsafe temporary-file creation and symlink race **Risk Level**: Medium ### Vulnerable Code ```bash # bin/voice-agent.sh:28 RECORDING_DIR="/tmp/voice-agent" ``` ```bash # bin/voice-agent.sh:82 mkdir -p "$RECORDING_DIR" ``` ```bash # bin/voice-agent.sh:162-169 while true; do # Record audio RECORDING_FILE="$RECORDING_DIR/recording_$(date +%s).wav" echo -e "${CYAN}[User]${NC}" echo -e "${BLUE}🎤 Recording for $DURATION seconds... (speak now)${NC}" ffmpeg -f dshow -i audio="Microphone" -t "$DURATION" -y "$RECORDING_FILE" 2>/dev/null || \ ffmpeg -f alsa -i default -t "$DURATION" -y "$RECORDING_FILE" 2>/dev/null ``` ### Technical Analysis The script uses a fixed directory beneath globally shared `/tmp` and does not: - Create it atomically. - Verify its owner or permissions. - Set a restrictive `umask`. - Use unpredictable recording names. - Verify that the destination is not a symbolic link. The filename contains only the current Unix timestamp in seconds, making it readily predictable. The `-y` option tells FFmpeg to overwrite the selected output without confirmation. On a multi-user system, an attacker may pre-create `/tmp/voice-agent` before the victim or race to prepare a predictable destination. Depending on directory ownership and filesystem protections, a symbolic link or attacker-controlled file can redirect or interfere with output creation. ### Attack Path 1. The attacker predicts when interactive recording will begin. 2. If the fixed directory does not yet exist, the attacker creates `/tmp/voice-agent` with attacker-controlled permissions. Alternatively, the attacker targets an installation where that directory is improperly shared. 3. The attacker predicts a path such as `/tmp/voice-agent/recording_1750000000.wav`. 4. The attacker places a symbolic link or conflicting file at that location. 5. The victim st ...[truncated 963 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Set `umask 077` before creating temporary audio. - Create a private, unpredictable directory atomically: ```bash umask 077 RECORDING_DIR="$(mktemp -d "${TMPDIR:-/tmp}/voice-agent.XXXXXXXX")" trap 'rm -rf -- "$RECORDING_DIR"' EXIT INT TERM ``` - Create each recording with `mktemp` rather than a timestamp: ```bash RECORDING_FILE="$(mktemp "$RECORDING_DIR/recording.XXXXXXXX.wav")" ``` - Verify that the temporary directory is owned by the current user and is not a symbolic link. - Avoid following pre-existing destination links. - Use cleanup traps so recordings are removed after errors and signals, not only after successful processing. - If recordings must persist, store them in a user-private data directory with mode `0700` rather than under shared `/tmp`. ]]>

T03 · Remote Payload Retrieval and Execution

Warning
Location
install.sh:50
Finding
Mutable Third-Party Source Is Downloaded, Built, and Executed Without Integrity Pinning<![CDATA[ ## Vulnerability Details **File Location**: `install.sh:50-59` **Additional Locations**: `SKILL.md:45-52, 67-74`; `README.md:41-52, 170-174, 195-200` **Vulnerability Type**: Remote mutable dependency retrieval and supply-chain execution **Risk Level**: Medium ### Vulnerable Code ```bash # install.sh:50-59 if [ ! -d "$HOME/.local/whisper.cpp" ]; then echo "" echo -e "${YELLOW}⚠️ Whisper.cpp not installed${NC}" echo -e "${BLUE}💡 Would you like to install it now? (y/n)${NC}" read -r INSTALL_WHISPER if [[ "$INSTALL_WHISPER" =~ ^[Yy]$ ]]; then echo -e "${YELLOW}📥 Installing Whisper.cpp...${NC}" git clone https://github.com/ggerganov/whisper.cpp ~/.local/whisper.cpp cd ~/.local/whisper.cpp make -j4 bash ./models/download-ggml-model.sh tiny ``` The documentation also recommends unpinned installation: ```bash # SKILL.md:45-52 # Clone and build git clone https://github.com/ggerganov/whisper.cpp ~/.local/whisper.cpp cd ~/.local/whisper.cpp make -j4 # Download tiny model (fast, low-resource) bash ./models/download-ggml-model.sh tiny ``` ```bash # README.md:195-200 # Install Python dependencies pip3 install pyyaml requests ``` ### Technical Analysis The installer clones the current default branch rather than a reviewed commit or signed release. It then immediately executes upstream build logic through `make` and runs an upstream model download script. The effective code executed during installation can therefore change after this Skill has been reviewed. The Python installation guidance similarly omits package versions and hash verification. Although `pyyaml` and `requests` are legitimate package names, unconstrained installation permits future versions to be selected without compatibility or integrity review. The Whisper installation requires explicit user confirmation, which reduces accidental execution but does not address authenticity, immutability, or supply-chain integrity. ## ...[truncated 1217 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Pin Whisper.cpp to a reviewed immutable commit or signed release tag. - Check out the exact commit before building and verify it: ```bash WHISPER_COMMIT="<reviewed-full-commit-hash>" git clone https://github.com/ggerganov/whisper.cpp "$HOME/.local/whisper.cpp" git -C "$HOME/.local/whisper.cpp" checkout --detach "$WHISPER_COMMIT" test "$(git -C "$HOME/.local/whisper.cpp" rev-parse HEAD)" = "$WHISPER_COMMIT" ``` - Verify signed tags or release signatures where available. - Download models from a fixed versioned URL and validate a documented SHA-256 digest before use. - Pin Python dependencies to reviewed versions. - Use a requirements file with hashes, for example `pip install --require-hashes -r requirements.txt`. - Install Python dependencies in an isolated virtual environment. - Record the reviewed dependency versions in the project documentation and update them through an explicit review process. - Preserve the current user-consent prompt, but do not treat consent as a replacement for integrity verification. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:67
Finding
Documentation Exposes the Unauthenticated TTS Service on All Network Interfaces<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:67-74` **Additional Locations**: `README.md:177-186`; `bin/voice-agent.sh:125-128`; `lib/tts.py:116-139` **Vulnerability Type**: Unsafe network-service binding and unauthenticated plaintext API use **Risk Level**: Medium ### Vulnerable Code ```bash # SKILL.md:67-74 **Option B: Install locally** ```bash # Clone your Pocket-TTS server cd /path/to/pockettts python3 -m venv venv source venv/bin/activate pip install -r requirements.txt python3 -m app.main --host 0.0.0.0 --port 5000 ``` ``` The same binding is recommended in the troubleshooting instructions: ```bash # README.md:179-183 # Start Pocket-TTS server cd /path/to/pockettts source venv/bin/activate python3 -m app.main --host 0.0.0.0 --port 5000 ``` The client then sends text to the configured server without authentication: ```python # lib/tts.py:116-127 tts_url = config['url'] endpoint = f"{tts_url.rstrip('/')}/v1/tts" payload = { 'text': text, 'voice': voice, 'format': format, 'sample_rate': sample_rate } try: response = requests.post(endpoint, json=payload, timeout=30) ``` ### Technical Analysis Binding a service to `0.0.0.0` makes it listen on every available IPv4 interface rather than only the local loopback interface. This conflicts with the project’s local-only security expectation. The client code does not send an authentication credential, and the documented URL uses plaintext HTTP. The actual Pocket-TTS server implementation is not part of this repository, so the precise set of remotely exposed endpoints cannot be established from the audited code. Nevertheless, following the supplied command makes the service network-reachable wherever host firewall rules permit it. The default client configuration uses `http://localhost:5000`, which is safer. The vulnerability arises from the server startup guidance repeated throughout the project. ### Attack Path 1. A user follows the documented Pocket-TTS startup ...[truncated 1147 chars]
Remediation
<![CDATA[ ## Remediation Suggestions - Change all default startup instructions to loopback-only binding: ```bash python3 -m app.main --host 127.0.0.1 --port 5000 ``` - Update error messages in `bin/voice-agent.sh` and `lib/tts.py` so they do not recommend `0.0.0.0`. - If remote operation is required, make it an explicit advanced configuration rather than the default. - Require authentication for remote API access. - Terminate TLS in the application or a trusted reverse proxy. - Restrict access with host firewall rules and network allowlists. - Reject non-loopback TTS URLs by default or require an explicit `allow_remote_tts` setting. - Warn users that a remote TTS URL transmits response text outside the local machine. - Apply request-size limits, rate limits, timeouts, and concurrency limits to reduce denial-of-service exposure. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (43)

Credential Access

High
Category
Privilege Escalation
Content
logs/

# Environment
.env
.venv/
venv/
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The description significantly overstates what this code does. The supplied script only converts provided text into speech audio and optionally plays it back. It does not capture microphone input, transcribe speech, perform assistant logic, or provide end-to-end voice-to-voice interaction. Additionally, while the description emphasizes fully local/offline operation, the script depends on a configurable TTS server URL and a Python wrapper that may make HTTP requests, so this chunk does not itself prove exclusively local processing. Therefore the declared description does not accurately represent the actual behavior of this code chunk.

Tp4

High
Category
MCP Tool Poisoning
Confidence
93% confidence
Finding
The description overstates completeness and functionality. The code does implement substantial voice pipeline behavior: dependency checks, microphone/audio-file input, transcription via a local STT script, TTS generation via a local helper, playback, and interactive looping. However, the central 'process_with_ai' function is explicitly a placeholder that only echoes canned text and says actual OpenClaw integration must be implemented. That makes the declared purpose of a complete OpenClaw voice assistant materially inaccurate. Additionally, while the setup appears intended for local use, it contacts a TTS server over HTTP and merely defaults to localhost based on config; the code chunk alone does not fully substantiate the strict '100% local processing' claim. The extra support for text input and audio-file processing is not harmful, but it is broader than the voice-only framing. Overall, this is a meaningful description-versus-behavior mismatch because the primary promised capability is not actually present.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding
The declared description presents a full offline voice-to-voice AI assistant with both STT and TTS capabilities and broader assistant use cases. The supplied code chunk only implements a command-line speech-to-text/transcription entrypoint. It checks for python3, PyYAML, and ffmpeg, parses model/language flags, and calls a local stt.py script on an audio file. That is consistent with a local transcription component, but it does not demonstrate the broader declared primary purpose of a complete voice-to-voice assistant, nor any text-to-speech, assistant logic, or voice cloning. This is therefore a material description-to-behavior mismatch rather than a mere partial implementation detail.

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding
The declared description describes a full offline voice-to-voice assistant, implying both speech input and spoken output plus assistant behavior. The supplied code only implements STT/transcription via Whisper.cpp and microphone capture via ffmpeg. It does operate locally and does not use cloud APIs, which is consistent with part of the description, but the primary scope of the code chunk is much narrower than the declared capability set. This is a material description-to-behavior mismatch rather than a minor omission.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The manifest describes a complete voice-to-voice AI assistant for OpenClaw, implying real end-to-end assistant behavior. However, the core AI step is stubbed out with placeholder output instead of invoking OpenClaw, so the implemented behavior materially falls short of the stated capability.

Description-Behavior Mismatch

High
Confidence
98% confidence
Finding
The configuration text explicitly labels the TTS endpoint as a 'VPS instance' while the skill metadata claims '100% local processing, no cloud APIs.' Even though the current URL is localhost, this contradiction is security-relevant because it can mislead users about where audio-derived data may be sent, causing unintended exposure of speech content or generated outputs if deployed with a non-local endpoint.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The README makes strong privacy/security claims such as '100% local processing' and 'No Cloud APIs' while also documenting a configurable HTTP TTS endpoint that can be pointed at a non-local server. This can mislead users into believing voice data never leaves the machine, increasing the risk of accidental transmission of sensitive audio or text to remote infrastructure.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
export POCKET_TTS_URL="http://localhost:5000"

# FFmpeg (Audio Conversion)
sudo apt-get install -y ffmpeg
```

### 2. Install Skill
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
export POCKET_TTS_URL="http://localhost:5000"

# FFmpeg (Audio Conversion)
sudo apt-get install -y ffmpeg
```

### 2. Install Skill
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
export POCKET_TTS_URL="http://localhost:5000"

# FFmpeg (Audio Conversion)
sudo apt-get install -y ffmpeg
```

### 2. Install Skill
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill advertises and documents capabilities that involve shell execution, filesystem access, and network communication, but it does not declare any explicit tool scope such as permissions or allowed-tools. That creates an authorization ambiguity where an agent may grant broader access than users expect, increasing the risk of unintended command execution, file access, or service communication.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
The documentation instructs users to send voice recordings and receive generated responses through an HTTP service without warning that this data may contain sensitive personal or operational information. Even when the default endpoint is localhost, a local or misconfigured service can log, retain, proxy, or expose recordings and transcriptions, leading to privacy and confidentiality risks.

Description-Behavior Mismatch

Medium
Confidence
85% confidence
Finding
The description emphasizes fully local processing, but the code treats TTS as an HTTP service identified by a configurable URL and probes it with `curl`. While the default points to localhost, the implementation permits non-local endpoints, so the code does not enforce the manifest's strict local-only claim.

Missing User Warnings

Medium
Confidence
89% confidence
Finding
This code records from the system microphone and writes the captured audio to a temporary WAV file, which is a privacy-relevant operation. Although it prints that recording is starting, it does not clearly warn the user that spoken audio will be captured and stored in /tmp before transcription.

Missing User Warnings

Medium
Confidence
88% confidence
Finding
Caching STT/TTS artifacts creates retention of audio-derived data on disk, which can expose sensitive spoken content, transcriptions, or synthesized outputs to other local users, backups, or forensic recovery. In a voice-assistant context, this is more dangerous because users may speak passwords, personal data, health information, or private requests without realizing those artifacts persist.

Session Persistence

Medium
Category
Rogue Agent
Content
SKILLS_DIR="$HOME/.openclaw/workspace/skills/voice-agent"
echo -e "${YELLOW}📦 Installing to: $SKILLS_DIR${NC}"

mkdir -p "$SKILLS_DIR"
cp -r "$VOICE_AGENT_DIR"/* "$SKILLS_DIR/"

# Make scripts executable
Confidence
60% 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.

Description-Behavior Mismatch

Medium
Confidence
92% confidence
Finding
The manifest emphasizes fully local processing with no cloud APIs, but this installer clones Whisper.cpp from GitHub and downloads a model over the network. While the runtime voice processing may remain local, the skill as delivered does perform remote network operations during installation, which materially exceeds the plain-language '100% local' claim.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
if ! command -v ffmpeg &> /dev/null; then
    echo ""
    echo -e "${YELLOW}⚠️  FFmpeg not installed${NC}"
    echo -e "${BLUE}💡 Install with: sudo apt-get install -y ffmpeg${NC}"
else
    echo -e "${GREEN}✅ FFmpeg already installed${NC}"
fi
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
if ! command -v ffmpeg &> /dev/null; then
    echo ""
    echo -e "${YELLOW}⚠️  FFmpeg not installed${NC}"
    echo -e "${BLUE}💡 Install with: sudo apt-get install -y ffmpeg${NC}"
else
    echo -e "${GREEN}✅ FFmpeg already installed${NC}"
fi
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
if ! command -v ffmpeg &> /dev/null; then
    echo ""
    echo -e "${YELLOW}⚠️  FFmpeg not installed${NC}"
    echo -e "${BLUE}💡 Install with: sudo apt-get install -y ffmpeg${NC}"
else
    echo -e "${GREEN}✅ FFmpeg already installed${NC}"
fi
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
return path

    # Try system path
    if subprocess.run(['which', 'whisper-cli'], capture_output=True).returncode == 0:
        return 'whisper-cli'

    raise FileNotFoundError(
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
wav_path = tmp.name

    try:
        subprocess.run([
            'ffmpeg', '-i', audio_path,
            '-ar', '16000',  # 16kHz sample rate
            '-ac', '1',      # Mono
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
'--no-timestamps'  # Clean output without timestamps
        ]

        result = subprocess.run(cmd, capture_output=True, text=True, check=True)

        # Extract transcription (last line after processing info)
        output_lines = result.stdout.strip().split('\n')
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
90% confidence
Finding
The function records from the microphone immediately and proceeds to transcription with only a console status message, not a clear consent or privacy warning. In a voice-agent skill, microphone capture is sensitive data collection; if invoked unexpectedly by another component, it can capture private speech without informed user consent.

Static analysis

No suspicious patterns detected.