T05 · Unauthorized Access and Privilege Escalation
- Location
scripts/server.py:120- Finding
Unauthenticated TTS Service Exposed on All Network Interfaces
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill does text-to-speech work, but it is advertised as local/offline while also shipping under-disclosed remote HTTP server/client modes that can expose user text and host resources.
Review this carefully before installing. Use it only if you are comfortable with model/package downloads and potential remote TTS operation. Do not run the HTTP server on 0.0.0.0 or install the LaunchAgent unless you add authentication, firewall/VPN restrictions, and preferably HTTPS. Avoid sending private text through --remote or QWEN_TTS_REMOTE unless you trust the endpoint and network path.
scripts/server.py:120Unauthenticated TTS Service Exposed on All Network Interfaces
scripts/server.py:40Unauthenticated Clients Can Select Arbitrary Models for Server-Side Retrieval and Loading
scripts/tts.py:103TTS Text and Voice Instructions Can Be Sent to Arbitrary Plaintext HTTP Endpoints
MAC_SERVER.md:168Persistent LaunchAgent Continuously Exposes the Unauthenticated Service
scripts/setup.sh:55Third-Party Dependencies Are Installed Without Version or Integrity Pinning
The core purpose does align broadly with text-to-speech generation using Qwen TTS and instruction-controlled voice synthesis. However, the declared description presents this as a local/offline TTS skill centered on a specific 1.7B model, while the code actually implements a network-accessible HTTP service for TTS. That is a materially undeclared capability because it exposes functionality over an API, including health and speaker enumeration endpoints, and can listen on all interfaces. Additionally, the code defaults to and accepts arbitrary model names, with the default set to Qwen3-TTS-12Hz-0.6B-CustomVoice rather than the declared 1.7B model. These are meaningful behavior differences, even though they support the same general TTS domain.
The code does perform text-to-speech-related work and supports voice description, language selection, and extra instructions, which broadly aligns with the TTS theme. However, the declared description emphasizes local/offline synthesis using a specific model and says it runs entirely offline after model download. The actual code does not load or run a local model at all; instead it requires a remote URL or environment variable and POSTs the synthesis request to a remote server. That is a material mismatch in primary deployment model and resource access. The code is therefore not accurately represented as an offline local TTS implementation.
The code substantially matches a text-to-speech purpose: it synthesizes speech from text, supports multiple languages, 9 named speakers, and instruction-based voice style control. However, the description says this is a local/offline alternative to cloud TTS that runs entirely offline after initial model download, while the code explicitly supports remote mode through --remote or QWEN_TTS_REMOTE and sends synthesis requests over the network. That is an undeclared capability and materially conflicts with the offline-only characterization. Additionally, the declared model is Qwen3-TTS-12Hz-1.7B-CustomVoice, but the script’s default model argument is Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice, which is a notable discrepancy in the represented model. Therefore the description does not fully and accurately represent the actual behavior.
The skill metadata claims the TTS runs entirely offline after initial model download, but this script explicitly requires a remote server URL and sends synthesis requests over HTTP. That mismatch can mislead users into disclosing sensitive text under a false privacy assumption, creating a trust and data exposure vulnerability.
The script transmits the full input text, language, voice description, and instructions to an arbitrary user-supplied remote URL. In the context of a purportedly local/offline TTS skill, this is especially dangerous because users may provide sensitive content expecting it to remain on-device, enabling unintended exfiltration to untrusted hosts.
os.system() and os exec-family calls run shell commands with the process's full privileges, enabling arbitrary command execution.
# Always use venv if available and not already active
if not os.environ.get("QWEN_TTS_VENV_ACTIVE") and VENV_PYTHON.exists():
os.environ["QWEN_TTS_VENV_ACTIVE"] = "1"
os.execv(str(VENV_PYTHON), [str(VENV_PYTHON)] + sys.argv)
# Check for remote mode
REMOTE_URL = os.environ.get("QWEN_TTS_REMOTE")
The manifest frames this skill as local, offline text-to-speech, but this file instructs users to expose it as an HTTP server for remote use. Serving TTS over the network is a materially broader operating mode than purely local offline use and changes the trust boundary described by the manifest.
The inline documentation explicitly says to run the system 'as HTTP server ... for remote TTS,' while the manifest describes a local offline TTS skill. This is an active contradiction in stated intent rather than a mere omission of implementation detail.
The guide instructs users to bind the TTS server to 0.0.0.0, exposing it on all network interfaces without authentication or access-control guidance. This can allow any host on the reachable network to access the service, submit text, consume resources, and potentially access sensitive prompts or generated content if the server or surrounding environment is not otherwise protected.
The documentation exposes the TTS service to the network and provides a concrete LAN IP and health-check URL, but does not warn about data exposure, unauthorized access, or the lack of authentication. In context, this makes the skill more dangerous because users may assume a 'local TTS' tool is private when they are actually publishing a service to other systems on the network.
The example sets QWEN_TTS_LANGUAGE to Italian, and later command examples also hard-code Italian usage. This constitutes a natural-language locale constraint in the skill documentation without opt-in, alternatives, or a stated region-specific justification.
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.
Create ~/Library/LaunchAgents/com.qwen.tts.plist:
<?xml version="1.0" encoding="UTF-8"?>
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.
Create ~/Library/LaunchAgents/com.qwen.tts.plist:
<?xml version="1.0" encoding="UTF-8"?>
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.
Create ~/Library/LaunchAgents/com.qwen.tts.plist:
<?xml version="1.0" encoding="UTF-8"?>
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.
Create ~/Library/LaunchAgents/com.qwen.tts.plist:
<?xml version="1.0" encoding="UTF-8"?>
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.
Create ~/Library/LaunchAgents/com.qwen.tts.plist:
<?xml version="1.0" encoding="UTF-8"?>
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.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
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.
Load:
launchctl load ~/Library/LaunchAgents/com.qwen.tts.plist
The skill advertises and demonstrates shell execution, environment-variable usage, and network access (model download and optional mirror) but does not declare any explicit tool scope or permissions. This creates a transparency and policy-enforcement gap: a host may invoke the skill assuming it is purely local/content-only while it can perform system and network actions.
The trigger text is broad enough to capture many generic audio-generation or voice-message requests, which can cause the skill to be selected in situations the user did not explicitly intend. Over-broad activation increases the chance of unexpected shell/network execution and makes downstream behavior less predictable in an agentic environment.
The request model exposes a caller-controlled model field and defaults to Qwen/Qwen3-TTS-12Hz-0.6B-CustomVoice, which conflicts with the manifest's claim of a fixed 1.7B local TTS model. Allowing arbitrary model selection can trigger unexpected remote downloads, larger resource consumption, and execution of unreviewed model assets, undermining the stated offline/local trust boundary.
The /tts endpoint lets any caller influence load_model(request.model), effectively turning a simple TTS API into a model-loading service. In context, this broadens attack surface by enabling denial of service through repeated model swaps, unbounded downloads, and use of unapproved models not necessary for normal text-to-speech behavior.
The server listens on 0.0.0.0 by default, exposing unauthenticated endpoints to the local network or beyond depending on host firewall/NAT configuration. Because /tts performs expensive model inference and can load caller-selected models, network exposure materially increases risk of unauthorized use and denial of service.
The file header and usage text describe this as a remote VoiceDesign client, which conflicts with the broader skill description of local offline TTS. In security-sensitive tooling, inconsistent documentation is dangerous because users and downstream agents may rely on the offline claim while actually invoking networked behavior.
This code performs external transmission via requests.post to a user-controlled remote server, sending potentially sensitive prompt content off-host. External transmission is not always vulnerable on its own, but here it is risky because the skill is marketed as offline and the endpoint is arbitrary, increasing the chance of privacy loss or misuse.
print(f"🎙️ Voice: {voice_desc[:60]}...", file=sys.stderr)
try:
response = requests.post(
f"{remote_url}/tts",
json={
"text": text,
No suspicious patterns detected.