MCP, MCP server
dev.voicemode/voicemode
Voice conversations with Claude Code using local speech-to-text and text-to-speech
Compatibility: Not stated.
Grade D
Two or more Medium findings, or a High finding. Only try it in a sandbox.
Not a guarantee. A hygiene check of the source at the commit shown, not a security audit.
Rescanned at commit e03b5d91a984, unchanged since the check below.
INSTALL_REMOTE_SCRIPT OBFUSCATION_ENCODED_BLOB
Why this grade
- INSTALL_REMOTE_SCRIPT Medium
Fetches a script from the network and runs it (a pipe to a shell, or an install hook with a URL).
installer/voicemode_install/installer.py, line 149
print(" curl -LsSf https://astral.sh/uv/install.sh | sh") - OBFUSCATION_ENCODED_BLOB Medium
Contains a long base64 shaped string in a file that should be readable.
uv.lock, line 1
version = 1
Any High, or two or more Medium findings, give D. One Medium gives C. Low findings or a security Note give B. Each code names a finding behind the letter.
If you think this grade is wrong, email [email protected] with the entry link and your evidence. We aim to acknowledge within two working days and decide within seven. If we confirm an error, we aim to correct it within 24 hours of confirming it. These are aims, not guarantees.
Grades are a hygiene check of the source at the commit shown, on the date checked, not a security audit or a guarantee. The free directory grades are automated. Before release, a PlugVet engineer reviews every paid report and any grade we hold back, including all F grades, against the source code and the evidence we quote.
What was checked
Plain notes from the hygiene checks.
Permissions
Note
requests shell access. The manifest names this permission.
PERMISSION_SHELL_DECLARED
installer/voicemode_install/checker.py, line 127
subprocess.run(
installer/voicemode_install/checker.py, line 141
subprocess.run(
installer/voicemode_install/checker.py, line 153
result = subprocess.run(
Show more
installer/voicemode_install/checker.py, line 175
subprocess.run(
installer/voicemode_install/cli.py, line 74
result = subprocess.run(
installer/voicemode_install/cli.py, line 93
result = subprocess.run(
installer/voicemode_install/cli.py, line 156
result = subprocess.run(install_script, shell=True, check=True, env=env)
installer/voicemode_install/cli.py, line 387
result = subprocess.run(
Install and update
Serious
install fetches a script from the network. The docs document an installer that pipes a download into a shell. README documents an installer that pipes a download into a shell.
INSTALL_REMOTE_SCRIPT INSTALL_DOCUMENTED_INSTALLER
CONTRIBUTING.md, line 10
- [Astral UV](https://github.com/astral-sh/uv) - Package manager (install with `curl -LsSf https://astral.sh/uv/install.sh | sh`)
README.md, line 52
curl -LsSf https://astral.sh/uv/install.sh | sh
installer/voicemode_install/installer.py, line 149
print(" curl -LsSf https://astral.sh/uv/install.sh | sh")
Show more
README.md, line 188
| UV not found | Run `curl -LsSf https://astral.sh/uv/install.sh \| sh` |
docs/tutorials/development-setup.md, line 21
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/tutorials/getting-started.md, line 23
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 6
# curl -fsSL https://getvoicemode.com/install.sh | bash
docs/web/install.sh, line 7
# curl -fsSL https://getvoicemode.com/install.sh | bash -s -- -y # non-interactive
docs/web/install.sh, line 8
# curl -fsSL https://getvoicemode.com/install.sh | bash -s -- -y --voice mlx
docs/web/install.sh, line 264
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 266
wget -qO- https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 301
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
docs/web/install.sh, line 303
wget -qO- https://sh.rustup.rs | sh -s -- -y --no-modify-path
docs/web/install.sh, line 440
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 442
wget -qO- https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 595
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/web/install.sh, line 597
wget -qO- https://astral.sh/uv/install.sh | sh
installer/README.md, line 37
- **uv** - Required to run the installer (`curl -LsSf https://astral.sh/uv/install.sh | sh`)
.claude/commands/install.md, line 79
- **UV** - Python package manager (install: `curl -LsSf https://astral.sh/uv/install.sh | sh`)
.github/workflows/create-release.yml, line 47
echo "curl -LsSf https://astral.sh/uv/install.sh | sh" >> $GITHUB_OUTPUT
.github/workflows/test.yml, line 39
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/.archive/development/TESTING-CHECKLIST.md, line 148
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/.archive/development/TESTING-CHECKLIST.md, line 159
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/.archive/install-robustness-analysis.md, line 40
curl -LsSf https://astral.sh/uv/install.sh | sh
docs/.archive/install-services-feature.md, line 53
curl -sSf https://getvoicemode.com/install.sh | sh
docs/.archive/livekit-service-design.md, line 29
2. **Linux**: Use official install script: `curl -sSL https://get.livekit.io | bash`
docs/.archive/npm-global-no-sudo.md, line 18
curl -fsSL https://raw.githubusercontent.com/mbailey/voicemode/master/scripts/setup-npm-global.sh | bash
docs/.archive/npm-global-no-sudo.md, line 89
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
docs/web/claude.html, line 629
curl -LsSf https://astral.sh/uv/install.sh | sh<br>
docs/web/index.html, line 597
curl -LsSf https://astral.sh/uv/install.sh | sh<br>
installer/voicemode_install/dependencies.yaml, line 76
install_command: curl -LsSf https://astral.sh/uv/install.sh | sh
voice_mode/dependencies.yaml, line 76
install_command: curl -LsSf https://astral.sh/uv/install.sh | sh
voice_mode/tools/kokoro/install.py, line 303
"curl -LsSf https://astral.sh/uv/install.sh | sh",
voice_mode/tools/mlx_audio/install.py, line 135
"curl -LsSf https://astral.sh/uv/install.sh | sh",
Network
Concern
can reach a host that is not written as a fixed address. can reach raw.githubusercontent.com. The description does not name that host. can reach api.openai.com. The description does not name that host.
NETWORK_NO_FIXED_HOST NETWORK_UNNAMED_HOST
docs/web/install.sh, line 268
die "Neither curl nor wget found. Cannot install uv."
docs/web/install.sh, line 305
die "Neither curl nor wget found. Cannot install Rust."
docs/web/install.sh, line 387
/bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
Show more
docs/web/install.sh, line 393
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
docs/web/install.sh, line 444
die "Neither curl nor wget found. Cannot install uv."
docs/web/install.sh, line 599
die "Neither curl nor wget found. Cannot install uv."
installer/voicemode_install/cli.py, line 155
install_script = '/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"'
voice_mode/mcp_launcher.py, line 15
No ``npx``/Node, no launch-time network fetch (see :mod:`voice_mode.mcp_bridge`).
docs/.archive/openai-api-routing.md, line 79
const response = await fetch('http://127.0.0.1:8880/v1/audio/speech', {
Secrets
Concern
reads a credential or environment file.
SECRETS_ENV_READ SECRETS_PATH_DOTENV
scripts/test-stt-direct.py, line 22
client = AsyncOpenAI(api_key=os.getenv("OPENAI_API_KEY"))
voice_mode/cli.py, line 2295
token = os.environ.get("VOICEMODE_MCP_TOKEN", "").strip() or None
voice_mode/config.py, line 734
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
Show more
voice_mode/config.py, line 737
CARTESIA_API_KEY = os.getenv("CARTESIA_API_KEY")
voice_mode/config.py, line 1673
SERVE_SECRET = os.getenv("VOICEMODE_SERVE_SECRET", "")
voice_mode/config.py, line 1676
SERVE_TOKEN = os.getenv("VOICEMODE_SERVE_TOKEN", "")
voice_mode/credential_store.py, line 202
store_type = os.getenv("VOICEMODE_CREDENTIAL_STORE", "plaintext").lower()
voice_mode/mcp_launcher.py, line 61
token = os.environ.get("VOICEMODE_MCP_TOKEN", "").strip() or None
voice_mode/pronounce.py, line 147
# Strip surrounding quotes if present (from .env file parsing)
.gitignore, line 8
# Environment configuration
.gitignore, line 9
.env.local
CHANGELOG.md, line 2764
- Removed outdated `.env.example` from root (superseded by auto-generated config)
CHANGELOG.md, line 2897
- Includes `.env.local.example` template with secure defaults
CHANGELOG.md, line 3544
- Export examples in .env.example for various setups
docs/.archive/LIVEKIT_SETUP.md, line 99
Create `~/.voicemode/frontend/.env.local` for custom settings:
docs/.archive/LIVEKIT_SETUP.md, line 251
**Frontend** (`.env.local`):
Agent instructions
Pass
No instruction to ignore earlier instructions or to hide a step.
Obfuscation
Concern
contains an encoded blob in a file that should be readable.
OBFUSCATION_ENCODED_BLOB
uv.lock, line 1
version = 1
voice_mode/mouth/__init__.py, line 1
"""mouth - the speaking half of the ears: ``mouth say TEXT`` returns at once.
docs/web/index-minimal-v3.html, line 10
src: url('data:font/woff2;base64,d09GMgABAAAAAAKgAA0AAAAAB8AAAAJOAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAABmAAgkIKgmSCFgsGAAE2AiQDCAQgBQYHLBttA8geh7HbZEkXzVA+8hE8v9bK9/v7cMBIgJBqYiTZU2BNROg
Licence
Pass
A licence file is present (LICENSE).
Maintainer activity
Pass
The latest commit is within the last 365 days.
Note
Hygiene check of commit e03b5d91a984. requests shell access. The manifest names this permission. install fetches a script from the network. The docs document an installer that pipes a download into a shell. README documents an installer that pipes a download into a shell. can reach a host that is not written as a fixed address. can reach raw.githubusercontent.com. The description does not name that host. can reach api.openai.com. The description does not name that host. reads a credential or environment file. contains an encoded blob in a file that should be readable.
Date and source
Date checked:
github.com
Commit graded: e03b5d91a984908032b848d9798ecb34a31a7491
This link goes to the original host. PlugVet does not host or check what you download from there, and installing it is your own decision and risk.