Back to skill

Security audit

Windows TTS (WSL2)

Security checks for vulnerabilities and agentic risk

Overview

This Windows text-to-speech skill is mostly purpose-aligned, but its speech script can turn spoken text or options into arbitrary PowerShell command execution.

Review before installing. Use this only with trusted text and options, because crafted text, voice, rate, or volume values may execute unintended PowerShell commands on the Windows side. A safer version should pass values as data and validate numeric ranges before invoking PowerShell.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/say.sh:41
Finding
PowerShell Command Injection Through Unsafely Interpolated Arguments<![CDATA[ ## Vulnerability Details **File Location**: `scripts/say.sh`, lines 41–59 **Vulnerability Type**: PowerShell command injection **Risk Level**: High ### Vulnerable Code ```bash PS="Add-Type -AssemblyName System.Speech; \$s=[System.Speech.Synthesis.SpeechSynthesizer]::new();" if [[ -n "$VOICE" ]]; then VOICE_ESC=$(printf '%s' "$VOICE" | sed 's/"/`"/g') PS+=" \$s.SelectVoice(\"$VOICE_ESC\");" fi if [[ -n "$RATE" ]]; then PS+=" \$s.Rate=[int]$RATE;" fi if [[ -n "$VOLUME" ]]; then PS+=" \$s.Volume=[int]$VOLUME;" fi TEXT_ESC=$(printf '%s' "$TEXT" | sed 's/"/`"/g') PS+=" \$s.Speak(\"$TEXT_ESC\");" powershell.exe -NoProfile -Command "$PS" >/dev/null ``` ### Technical Analysis The script constructs executable PowerShell source by concatenating command-line arguments into the `PS` variable and then passes the resulting string to `powershell.exe -Command`. The attempted sanitization of `VOICE` and `TEXT` only escapes double-quote characters. These values remain inside PowerShell double-quoted strings, where PowerShell evaluates expandable expressions, including `$()` subexpressions. Consequently, attacker-controlled text containing a PowerShell subexpression can cause commands to execute while PowerShell evaluates the argument to `Speak()` or `SelectVoice()`. The `RATE` and `VOLUME` arguments present an additional and more direct injection path. They are inserted into PowerShell source without quoting, numeric validation, or range validation. Casting the value with `[int]` does not make the construction safe because an attacker can introduce PowerShell statement separators and append another statement after a syntactically valid numeric expression. The documented expected ranges—`-10..10` for rate and `0..100` for volume—are not enforced. ### Attack Path 1. An attacker gains control over text or options passed to `scripts/say.sh`, such as through an untrusted user request processed by an agent. 2. The attacker supplies either: - speech t ...[truncated 1329 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. **Do not concatenate user-controlled values into PowerShell source.** Use a fixed PowerShell script and transfer text, voice, rate, and volume as data through environment variables, standard input, or safely serialized parameters. 2. **Avoid `Invoke-Expression` and equivalent dynamic evaluation.** PowerShell should parse only trusted, static program text. 3. **Validate numeric arguments in Bash before invoking PowerShell.** - Require `RATE` to match an integer-only pattern and enforce the range `-10` through `10`. - Require `VOLUME` to match an integer-only pattern and enforce the range `0` through `100`. - Reject invalid values rather than attempting to sanitize them. 4. **Validate voice selection.** Retrieve installed voice names and require the requested name to exactly match an installed voice. Even after validation, pass the name as data rather than source code. 5. **Use a static PowerShell command that reads environment variables**, for example: ```bash if [[ -n "$RATE" ]]; then [[ "$RATE" =~ ^-?[0-9]+$ ]] || { printf 'Invalid rate: expected an integer from -10 to 10\n' >&2 exit 1 } (( RATE >= -10 && RATE <= 10 )) || { printf 'Invalid rate: expected an integer from -10 to 10\n' >&2 exit 1 } fi if [[ -n "$VOLUME" ]]; then [[ "$VOLUME" =~ ^[0-9]+$ ]] || { printf 'Invalid volume: expected an integer from 0 to 100\n' >&2 exit 1 } (( VOLUME >= 0 && VOLUME <= 100 )) || { printf 'Invalid volume: expected an integer from 0 to 100\n' >&2 exit 1 } fi TTS_TEXT="$TEXT" \ TTS_VOICE="$VOICE" \ TTS_RATE="$RATE" \ TTS_VOLUME="$VOLUME" \ powershell.exe -NoProfile -Command ' Add-Type -AssemblyName System.Speech $s = [System.Speech.Synthesis.SpeechSynthesizer]::new() if ($env:TTS_VOICE) { $s.SelectVoice($env:TTS_VOICE) } if ($env:TTS_RATE) { $s.Rate = [int]$env:TTS_RATE } if ($env:TTS_VOLUME) { $s.Volume = [int]$env:TTS_VOLUME } $s.Speak($env:TTS_TEXT) ' ``` 6 ...[truncated 205 chars]
Vulnerability Patterns
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (2)

Natural-Language Policy Violations

Medium
Confidence
84% confidence
Finding
The description steers behavior toward a specific Chinese-language/Windows-11 TTS flow without clearly requiring user preference selection or consent, which can cause the agent to make locale and output-channel assumptions on the user's behalf. In security-sensitive agent environments, forcing a platform/language-specific behavior can increase the chance of unintended command execution or privacy-impacting audio output in the wrong context.

Vague Triggers

Medium
Confidence
95% confidence
Finding
The trigger phrases are very broad and match common conversational requests like 'read it out' or 'no sound', which can cause the skill to activate when the user did not explicitly intend to use this Windows/PowerShell-based TTS path. In this context, unintended invocation can lead to unexpected audio playback and execution of an external host command path, making the issue more than a mere UX problem.

Static analysis

No suspicious patterns detected.