Back to skill

Security audit

Her Voice

Security checks for vulnerabilities and agentic risk

Overview

This local voice skill mostly matches its stated purpose, but it needs review because setup has a real configurable model-name code execution risk and relies on mutable third-party installs plus a background daemon.

Install only if you are comfortable with a local background TTS service, large RAM use, clipboard paste-to-speak in persist mode, and setup downloading unpinned Python packages/model assets. On Apple Silicon, avoid setting a custom model value until the python -c interpolation bug is fixed, and do not let an agent run the uninstall command without explicit confirmation.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/setup.py:324
Finding

Arbitrary Python Code Execution Through the Configurable Model Identifier

Content
View full analysis
None: """Download the Kokoro model by doing a test load (MLX).""" print_step(f"Downloading model: {model_name}...") python = os.path.join(venv_path, "bin", "python3") result = subprocess.run([ python, "-c", f"from mlx_audio.tts.utils import load_model; m = load_model(model_path='{model_name}'); print('Model loaded, sample_rate:', getattr(m, 'sample_rate', 24000))" ], capture_output=True, text=True, timeout=300) ``` The model value is obtained from configuration when setup invokes this function: ```python # scripts/setup.py:477-480 if engine == "mlx": download_model_mlx(venv_path, config.get("model", "mlx-community/Kokoro-82M-bf16")) else: download_model_pytorch(venv_path) ``` The documented configuration command permits an unrestricted string to be written: ```python # scripts/config.py:276-279 def cmd_set(key: str, value: str) -> None: """Set a config value.""" config = load_config() set_value(config, key, value) save_config(config) ``` ### Technical Analysis The `model_name` configuration value is interpolated directly into Python source code passed to a separate interpreter through the `-c` option. It is placed inside a single-quoted Python string without escaping or validation. An attacker-controlled value containing a single quote, closing parenthesis, statement separator, and comment marker can terminate the intended `load_model()` call and insert arbitrary Python statements. For example, a value conceptually shaped as: ```text x'); __import__('os').system('ATTACKER_COMMAND'); # ``` would transform the generated source into c ...[truncated 1930 chars]
Remediation
View remediation

T08 · Insecure Dependencies

Warning
Location
scripts/setup.py:87
Finding

Unpinned Third-Party Packages and Model Assets Create a Supply-Chain Risk

Content
View full analysis
=0.8", "soundfile", "numpy"], check=True) print_ok(f"Installed kokoro at: {PYTORCH_VENV_DIR}") return PYTORCH_VENV_DIR ``` Model loading also does not pin a repository revision: ```python # scripts/setup.py:324-348 def download_model_mlx(venv_path: str, model_name: str = "mlx-community/Kokoro-82M-bf16") -> None: """Download the Kokoro model by doing a test load (MLX).""" print_step(f"Downloading model: {model_name}...") python = os.path.join(venv_path, "bin", "python3") result = subprocess.run([ ...[truncated 3369 chars]
Remediation
View remediation
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
  • Tool MisuseTool Parameter Abuse, Chaining Abuse, Unsafe Defaults
  • Rogue AgentSelf-Modification, Session Persistence
Findings (38)

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

The skill recommends a destructive shell command, rm -rf ~/.her-voice, that recursively deletes data from the user's home directory. Although scoped to the app directory, any automated execution, path expansion mistake, or variable substitution issue in a broader agent context could cause unintended deletion, so exposing raw destructive commands in agent-facing instructions is risky.

Content

Scanner excerpt · SKILL.md (reported line 275)May include surrounding context.

Remove all Her Voice data (config, venvs, compiled binary, daemon state):

bash
python3 SKILL_DIR/scripts/daemon.py stop
rm -rf ~/.her-voice

How It Works

Tool Parameter Abuse

High
Category
Tool Misuse
Confidence
90% confidence
Finding

The skill recommends a destructive shell command, rm -rf ~/.her-voice, that recursively deletes data from the user's home directory. Although scoped to the app directory, any automated execution, path expansion mistake, or variable substitution issue in a broader agent context could cause unintended deletion, so exposing raw destructive commands in agent-facing instructions is risky.

Content

Scanner excerpt · SKILL.md (reported line 275)May include surrounding context.

Remove all Her Voice data (config, venvs, compiled binary, daemon state):

bash
python3 SKILL_DIR/scripts/daemon.py stop
rm -rf ~/.her-voice

How It Works

os.system() or os exec-family call

High
Category
Dangerous Code Execution
Confidence
85% confidence
Finding

os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.

Content

Scanner excerpt · scripts/config.py (reported line 129)May include surrounding context.

python
print("Ensure the path points to a Python interpreter inside a valid venv.", file=sys.stderr)
        sys.exit(1)

    os.execv(python, [python] + sys.argv)


def detect_engine(config: dict[str, Any]) -> str:

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill explicitly instructs the agent to run shell commands, write config under the user's home directory, start background processes, and remove files, but it declares no tool scope or allowed-tools metadata. This creates an authorization gap where a host agent may grant broader capabilities than necessary, increasing the blast radius if the skill is invoked unexpectedly or modified later.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · SKILL.md (reported line 42)May include surrounding context.

  1. Patch espeak loader if needed (macOS compatibility)
  2. Compile the native visualizer binary (macOS only)
  3. Download the Kokoro model
  4. Create config at ~/.her-voice/config.json

Check status anytime:

bash

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

The use of disown reinforces deliberate session-detached persistence for the TTS daemon. Even though the purpose is performance optimization, detached background execution can surprise users, consume significant RAM, and leave a long-lived local service available to other local processes if not carefully permissioned.

Content

Scanner excerpt · SKILL.md (reported line 129)May include surrounding context.

bash
# Start (persists in background)
nohup python3 SKILL_DIR/scripts/daemon.py start > /tmp/her-voice-daemon.log 2>&1 & disown

# Status
python3 SKILL_DIR/scripts/daemon.py status

Session Persistence

Medium
Category
Rogue Agent
Confidence
91% confidence
Finding

The use of disown reinforces deliberate session-detached persistence for the TTS daemon. Even though the purpose is performance optimization, detached background execution can surprise users, consume significant RAM, and leave a long-lived local service available to other local processes if not carefully permissioned.

Content

Scanner excerpt · SKILL.md (reported line 129)May include surrounding context.

bash
# Start (persists in background)
nohup python3 SKILL_DIR/scripts/daemon.py start > /tmp/her-voice-daemon.log 2>&1 & disown

# Status
python3 SKILL_DIR/scripts/daemon.py status

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The uninstall section provides a recursive deletion command for a home-directory path without a strong warning that the action permanently removes user configuration, binaries, daemon state, and downloaded assets. While the target path is specific, users or agents may execute it without appreciating the data-loss consequences.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

The manifest describes a skill for giving the agent a voice when speech output is needed. This file does more than playback/visualization: it reads local configuration, connects to a local Unix socket TTS daemon, and turns pasted clipboard text into synthesized speech, which is a broader operational scope than a simple voice-output helper.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
88% confidence
Finding

On Cmd+V, the code reads arbitrary text from the global pasteboard and sends it into speech generation. A skill described only as giving the agent a voice would be expected to speak provided text, but not necessarily inspect the user's clipboard contents as an input source.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

When the user presses Cmd+V, clipboard text is automatically sanitized and sent to the local TTS daemon without any visible disclosure, confirmation, or preview. Even though the trigger is user-initiated, clipboard contents may contain secrets or sensitive data, and silently forwarding them to another process increases confidentiality risk and can violate user expectations.

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 · scripts/setup.py (reported line 70)May include surrounding context.

python
python = os.path.join(venv_path, "bin", "python3")
        if os.path.exists(python):
            try:
                result = subprocess.run(
                    [python, "-c", "import mlx_audio; print(mlx_audio.__file__)"],
                    capture_output=True, text=True, timeout=10
                )

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 · scripts/setup.py (reported line 91)May include surrounding context.

python
print_warn("mlx-audio not found. Installing...")
    os.makedirs(MLX_VENV_DIR, exist_ok=True)
    subprocess.run([sys.executable, "-m", "venv", MLX_VENV_DIR], check=True)
    pip = os.path.join(MLX_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing mlx-audio + numpy (this may take a few minutes)...")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
82% confidence
Finding

This setup script performs an automatic pip self-upgrade and executes packages retrieved from the network without any pinning, hash verification, or trust controls. While common in installers, it increases supply-chain risk because a compromised package index, dependency, or network path could lead to arbitrary code execution during installation.

Content

Scanner excerpt · scripts/setup.py (reported line 93)May include surrounding context.

python
os.makedirs(MLX_VENV_DIR, exist_ok=True)
    subprocess.run([sys.executable, "-m", "venv", MLX_VENV_DIR], check=True)
    pip = os.path.join(MLX_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing mlx-audio + numpy (this may take a few minutes)...")
    subprocess.run([pip, "install", "mlx-audio", "numpy"], check=True)
    print_ok(f"Installed mlx-audio at: {MLX_VENV_DIR}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
91% confidence
Finding

The script automatically installs third-party packages from PyPI at setup time, and pip installation can execute arbitrary code through build backends, install hooks, or compromised dependencies. Because versions are not pinned or hash-verified, the skill introduces a real supply-chain execution risk on the host system.

Content

Scanner excerpt · scripts/setup.py (reported line 95)May include surrounding context.

python
pip = os.path.join(MLX_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing mlx-audio + numpy (this may take a few minutes)...")
    subprocess.run([pip, "install", "mlx-audio", "numpy"], check=True)
    print_ok(f"Installed mlx-audio at: {MLX_VENV_DIR}")
    return MLX_VENV_DIR

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 · scripts/setup.py (reported line 105)May include surrounding context.

python
print_step("Checking PyTorch Kokoro...")
    python = os.path.join(PYTORCH_VENV_DIR, "bin", "python3")
    if os.path.exists(python):
        result = subprocess.run(
            [python, "-c", "import kokoro; print('ok')"],
            capture_output=True, text=True, timeout=10
        )

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 · scripts/setup.py (reported line 115)May include surrounding context.

python
print_warn("PyTorch Kokoro not found. Installing...")
    os.makedirs(PYTORCH_VENV_DIR, exist_ok=True)
    subprocess.run([sys.executable, "-m", "venv", PYTORCH_VENV_DIR], check=True)
    pip = os.path.join(PYTORCH_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing kokoro + soundfile + numpy (this may take a few minutes)...")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
82% confidence
Finding

Automatically upgrading pip inside a newly created environment pulls executable code from the network without version pinning or integrity verification. That creates a supply-chain exposure even though there is no command injection issue.

Content

Scanner excerpt · scripts/setup.py (reported line 117)May include surrounding context.

python
os.makedirs(PYTORCH_VENV_DIR, exist_ok=True)
    subprocess.run([sys.executable, "-m", "venv", PYTORCH_VENV_DIR], check=True)
    pip = os.path.join(PYTORCH_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing kokoro + soundfile + numpy (this may take a few minutes)...")
    subprocess.run([pip, "install", "kokoro>=0.8", "soundfile", "numpy"], check=True)
    print_ok(f"Installed kokoro at: {PYTORCH_VENV_DIR}")

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
93% confidence
Finding

Installing kokoro, soundfile, and numpy directly from package repositories during setup can result in arbitrary code execution if a dependency or repository is compromised. The risk is amplified by the partially unpinned requirement 'kokoro>=0.8', which allows future unreviewed releases.

Content

Scanner excerpt · scripts/setup.py (reported line 119)May include surrounding context.

python
pip = os.path.join(PYTORCH_VENV_DIR, "bin", "pip")
    subprocess.run([pip, "install", "--upgrade", "pip"], check=True, capture_output=True)
    print("   Installing kokoro + soundfile + numpy (this may take a few minutes)...")
    subprocess.run([pip, "install", "kokoro>=0.8", "soundfile", "numpy"], check=True)
    print_ok(f"Installed kokoro at: {PYTORCH_VENV_DIR}")
    return PYTORCH_VENV_DIR

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 · scripts/setup.py (reported line 144)May include surrounding context.

python
print_err("  1. Install Homebrew: https://brew.sh")
            print_err("  2. brew install espeak-ng")
            sys.exit(1)
        subprocess.run(["brew", "install", "espeak-ng"], check=True)
        print_ok("espeak-ng installed")
    elif platform.system() == "Linux":
        if shutil.which("apt-get"):

Sudo/Root Execution

Medium
Category
Privilege Escalation
Confidence
70% confidence
Finding

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

Content

Scanner excerpt · scripts/setup.py (reported line 149)May include surrounding context.

python
elif platform.system() == "Linux":
        if shutil.which("apt-get"):
            print_err("espeak-ng not found. Install it with:")
            print_err("  sudo apt-get install espeak-ng")
        else:
            print_err("espeak-ng not found. Please install it for your distribution.")
        sys.exit(1)

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 · scripts/setup.py (reported line 175)May include surrounding context.

python
except Exception as e:
    print(f"FAIL:{e}")
"""
    result = subprocess.run([python, "-c", test_code], capture_output=True, text=True, timeout=30)
    if result.stdout.strip() == "OK":
        print_ok("espeak integration works — no patch needed")
        return

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 · scripts/setup.py (reported line 185)May include surrounding context.

python
import misaki.espeak as me
print(me.__file__)
"""
    result = subprocess.run([python, "-c", find_code], capture_output=True, text=True, timeout=10)
    if result.returncode != 0:
        print_warn("Could not locate misaki espeak module — skipping patch")
        return

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 · scripts/setup.py (reported line 267)May include surrounding context.

python
print_err(f"Source not found: {swift_src}")
        sys.exit(1)

    result = subprocess.run([
        "swiftc", "-o", binary, swift_src,
        "-framework", "Cocoa", "-framework", "AVFoundation"
    ], capture_output=True, text=True)

Session Persistence

Medium
Category
Rogue Agent
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.

Content

Scanner excerpt · scripts/setup.py (reported line 281)May include surrounding context.

python
def create_config(engine: str, venv_path: str, viz_binary: str) -> dict:
    """Create or update config with discovered paths."""
    print_step("Setting up config...")

    sys.path.insert(0, os.path.join(SKILL_DIR, "scripts"))

Static analysis

No suspicious patterns detected.