T09 · Insecure Skill Coding Practices
- Location
scripts/speechfy-tts.py:89- Finding
Predictable Temporary Files Allow Local File Clobbering
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This is a coherent text-to-speech skill that sends provided text to documented TTS providers and writes audio output, with some privacy and hardening considerations but no evidence of hidden or malicious behavior.
Install only if you are comfortable sending TTS text to Speechify and, on fallback, Microsoft Edge TTS. Avoid using it for secrets or regulated data, use trusted paths for helper commands, prefer an isolated Python environment with pinned dependencies, and avoid shared predictable output paths under /tmp when possible.
scripts/speechfy-tts.py:89Predictable Temporary Files Allow Local File Clobbering
README.md:11Unpinned Third-Party Dependency Installation
Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.
)
try:
resp = urllib.request.urlopen(req, timeout=30)
data = json.loads(resp.read())
audio_b64 = data.get("audio_data", "")
if not audio_b64:
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# Speechify TTS — Credentials
# Copy this to .env and fill in your values.
# .env is gitignored — never commit real credentials.
# Speechify API key (required for primary TTS)
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.
# Speechify TTS — Credentials
# Copy this to .env and fill in your values.
# .env is gitignored — never commit real credentials.
# Speechify API key (required for primary TTS)
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.
import subprocess
def generate_speech(text, output="/tmp/speech.ogg"):
proc = subprocess.run(
["python3", "scripts/speechfy-tts.py", text, output],
capture_output=True, text=True, timeout=60
)
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.
import subprocess
def generate_speech(text, output="/tmp/speech.ogg"):
proc = subprocess.run(
["python3", "scripts/speechfy-tts.py", text, output],
capture_output=True, text=True, timeout=60
)
Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.
import subprocess
output = "/tmp/response.ogg"
subprocess.run([
"python3", "scripts/speechfy-tts.py",
text, output
], check=True)
Hidden instructions were detected in comments or invisible text. These could contain malicious directives. Manual review is recommended.
<rect width="800" height="520" fill="url(#bg)" rx="12"/>
<text x="400" y="38" text-anchor="middle" font-size="22" font-weight="bold" fill="#2d3436">Speechify TTS — Fluxo de Operação</text>
<!-- Input -->
<rect x="300" y="60" width="200" height="50" rx="10" fill="#636e72" filter="url(#shadow)"/>
<text x="400" y="82" text-anchor="middle" font-size="14" font-weight="bold" fill="#fff">📝 Texto (CLI / SSML)</text>
<text x="400" y="100" text-anchor="middle" font-size="10" fill="#dfe6e9">Argumento da linha de comando</text>
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# 1. Install dependencies
pip install edge-tts # for fallback (optional)
sudo apt install ffmpeg # for audio conversion
# 2. Set your API key
export SPEECHIFY_API_KEY="sk_..."
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# 1. Install dependencies
pip install edge-tts # for fallback (optional)
sudo apt install ffmpeg # for audio conversion
# 2. Set your API key
export SPEECHIFY_API_KEY="sk_..."
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.
# 1. Install dependencies
pip install edge-tts # for fallback (optional)
sudo apt install ffmpeg # for audio conversion
# 2. Set your API key
export SPEECHIFY_API_KEY="sk_..."
The README explicitly documents automatic fallback to Edge TTS but does not warn that the input text will be transmitted to a different third-party provider when Speechify fails. In a TTS skill, text may contain sensitive prompts, personal data, or secrets, so silent provider failover changes the data-processing boundary and creates a real privacy and compliance risk.
The skill clearly describes capabilities that require shell execution, environment-variable access, and outbound network access to external TTS providers, but it does not declare an explicit tool/permission scope. This can cause users or orchestration systems to invoke the skill without understanding its operational privileges, increasing the risk of unintended data egress and command execution.
The skill sends user-supplied text to third-party TTS services over the network, but the description does not prominently warn that content may leave the local environment. If users submit sensitive messages, credentials, or personal data, this omission can lead to unintended disclosure to external providers.
The SVG includes natural-language content specifying the fallback voice as "FranciscaNeural (pt-BR)", which indicates a fixed locale selection. Under the policy, forcing a specific language or locale without offering user choice or documenting a justified region-specific constraint is a violation.
The reference specifies only Portuguese voice options, including pt-BR fallback voices and a PT-PT section, but does not mention user opt-in, alternative locales, or a justification for restricting output to Portuguese variants. This can violate the language/locale policy because it implicitly enforces a locale choice without documenting consent or necessity.
The manifest describes a multi-provider text-to-speech generator, but this code also attempts to locate and execute a separate vault-resolver program to retrieve credentials. Secret-resolution via an external executable is not mentioned in the stated purpose and goes beyond the obvious core function of generating speech audio.
The script executes a helper binary whose path is taken from the VAULT_RESOLVER environment variable, with only a minimal os.path.isfile check. In environments where attackers can influence environment variables or working configuration, this can lead to execution of an arbitrary local program under the agent's privileges and exposure of requested secret material.
vault_resolver = os.environ.get("VAULT_RESOLVER", "vault-resolver")
if os.path.isfile(vault_resolver):
try:
proc = subprocess.run(
[vault_resolver, "resolve"],
input=json.dumps({"ids": [VAULT_ITEM]}).encode(),
capture_output=True, timeout=20
This is a real unsafe tainted flow because an environment-controlled executable path is launched directly. In agent or automation contexts, environment variables are often easier to influence than code, so this creates a realistic arbitrary program execution vector and could be abused to steal secrets or run unintended code.
vault_resolver = os.environ.get("VAULT_RESOLVER", "vault-resolver")
if os.path.isfile(vault_resolver):
try:
proc = subprocess.run(
[vault_resolver, "resolve"],
input=json.dumps({"ids": [VAULT_ITEM]}).encode(),
capture_output=True, timeout=20
The script sends arbitrary input text to external TTS providers without any explicit warning, confirmation, or filtering. In an agent setting, users may provide sensitive prompts, credentials, or personal data, so silent third-party transmission can create meaningful privacy and compliance exposure.
This skill performs external transmission of user-provided text and an authorization credential to a third-party API. In the context of an agent skill, that is security-relevant because users may not expect their content to leave the local environment, and sensitive text could be disclosed to a vendor service.
}
req = urllib.request.Request(
"https://api.speechify.ai/v1/audio/speech",
data=json.dumps(payload).encode(),
headers={
"Authorization": f"Bearer {key}",
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
mp3_tmp = output_path + ".tmp.mp3"
with open(mp3_tmp, "wb") as f:
f.write(base64.b64decode(audio_b64))
subprocess.run(
["ffmpeg", "-y", "-i", mp3_tmp,
"-c:a", "libopus", "-b:a", "32k",
output_path],
Although audio generation is aligned with the skill purpose, the implementation achieves it by executing local programs for media conversion and fallback synthesis. The manifest description mentions providers and output format, but not that the skill executes host-installed binaries, which is a materially broader capability than a pure API-backed TTS skill.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
edge_cmd = resolve_edge_cmd()
tmp = output_path + ".edge.mp3"
try:
subprocess.run(
[edge_cmd, "--voice", EDGE_VOICE,
"--text", text,
"--write-media", tmp],
Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.
edge_cmd = resolve_edge_cmd()
tmp = output_path + ".edge.mp3"
try:
subprocess.run(
[edge_cmd, "--voice", EDGE_VOICE,
"--text", text,
"--write-media", tmp],
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
)
# edge-tts grava MP3 mesmo com extensão .ogg — converte para Opus
# para garantir voice bubble nativa no Telegram.
subprocess.run(
["ffmpeg", "-y", "-i", tmp,
"-c:a", "libopus", "-b:a", "32k",
output_path],
No suspicious patterns detected.