T09 · Insecure Skill Coding Practices
- Location
scripts/tts_server.py:413- Finding
Unauthenticated Network Service Accepts Unrestricted Local File Paths
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
The skill mostly does what it claims, but its optional TTS server is under-secured and could expose local file operations and heavy compute work to network clients.
Review before installing, especially if you will use the HTTP TTS server or process confidential presentations. Prefer Edge TTS only for content you are comfortable sending to an online service, bind any local TTS server to 127.0.0.1, avoid exposing it to a network without authentication, and pin dependencies/model revisions for repeatable installs.
scripts/tts_server.py:413Unauthenticated Network Service Accepts Unrestricted Local File Paths
scripts/restart_tts.sh:19Broad Process Matching Can Terminate Unrelated Processes
SKILL.md:63Unpinned Python Dependencies and Mutable Model Artifact
scripts/restart_tts.sh:49Detached TTS Server Lacks Safe Lifecycle and Exposure Controls
The declared description presents a full presentation-to-video pipeline, including document conversion, speaker notes handling, multiple TTS modes, and JSON subtitle input. The supplied code does not implement those end-to-end capabilities. Instead, it assumes slide images and WAV audio already exist and focuses narrowly on assembling them into an MP4 with optional synchronized subtitle overlays. Batch processing is partially consistent, and subtitle syncing/video generation are consistent, but the primary purpose in this chunk is materially narrower than the declared end-to-end pipeline. Therefore this is a description-behavior mismatch for the supplied code chunk.
The declared description presents a broad end-to-end presentation-to-narrated-video system with document conversion, TTS generation, subtitle handling, and batch processing. The supplied code chunk does not implement those capabilities. Its purpose is narrowly limited to merging already-generated slide MP4 files into one final MP4 via FFmpeg, with compatibility checks and optional re-encoding. While such a merger could be one supporting component within a larger pipeline, this code chunk by itself materially differs from the declared primary purpose and lacks the core described functionality.
The declared description presents a full pipeline for turning presentations into narrated MP4 videos with subtitles and multiple TTS backends. The supplied code chunk does not implement that overall behavior. It performs only a narrow preprocessing task: converting PDF pages into PNG images at configured resolutions. While this could be a supporting component of the larger pipeline, the actual code chunk's behavior is materially narrower than the declared purpose and lacks the core advertised capabilities such as TTS, audio validation, subtitle syncing, video assembly, or PPT/PPTX handling. Therefore, the description does not accurately represent what this specific code chunk actually does.
The code largely matches the general theme of creating narrated slide videos, but the declared description significantly overstates implemented capabilities. The actual script is specifically a PDF + JSON-subtitles to MP4 pipeline using Edge TTS. It does not accept PPT/PPTX as inputs in this chunk, does not extract speaker notes from presentation files, and lacks the advertised alternate TTS backends and LibreOffice-based PPTX conversion. Subtitle timing is approximated from text length rather than robustly auto-synced from speech analysis, though this alone is less severe than the missing core features. Because several prominent declared capabilities are absent, this is a material description-behavior mismatch.
The declared description is for a broad presentation-to-video pipeline, but the supplied code chunk is only one component: a local HTTP TTS service for Qwen3-TTS. Its primary purpose is speech synthesis and batch generation of WAV audio for slide notes from script.md. While TTS is related to the larger system, the code does not implement the core declared outcomes: converting presentation files, handling JSON subtitles, producing MP4 output, or coordinating multiple TTS modes such as Edge TTS. This is a material description-behavior mismatch rather than a minor implementation-detail difference.
The documented --skip-validation option disables the audio quality-control step that is presented as a core safeguard. If users rely on validation to catch corrupted, mismatched, or manipulated narration, bypassing it can let incorrect or maliciously substituted output pass through unnoticed.
python scripts/pipeline.py --force-audio
# Skip audio validation (use existing audio as-is)
python scripts/pipeline.py --skip-validation
# Custom validation threshold
python scripts/pipeline.py --threshold 0.7 --max-retries 3
The --skip-validation flag disables the only quality-control check that verifies generated or existing audio matches the intended slide text, yet the pipeline will still accept and publish output. In an automation context, this can be abused to pass tampered, stale, or mismatched audio into final videos, undermining output integrity and potentially enabling misleading or malicious content insertion.
help="Skip image generation (use existing images)",
)
parser.add_argument(
"--skip-validation", action="store_true",
help="Skip Stage 2 audio validation (use existing audio as-is)",
)
parser.add_argument(
Remote code is downloaded and executed. This bypasses code review and could introduce malicious code.
while [ $WAITED -lt $HEALTH_TIMEOUT ]; do
RESP=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:$PORT/health" 2>/dev/null || echo "000")
if [ "$RESP" = "200" ]; then
MODEL_LOADED=$(curl -s "http://localhost:$PORT/health" 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin).get('model_loaded', False))" 2>/dev/null || echo "False")
if [ "$MODEL_LOADED" = "True" ]; then
echo " ✅ TTS server is ready!"
echo ""
The skill advertises and instructs use of capabilities equivalent to file read/write, network access, and shell execution, but does not declare any explicit tool scope or permissions. This weakens least-privilege controls and can cause the agent to use broader capabilities than users or hosting systems expect.
The skill makes Edge TTS the default, which transmits slide text or speaker notes to an external online service, but the description does not present a prominent privacy warning. Users may unknowingly send confidential presentation content off-device, creating data exposure and compliance risks.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
python scripts/pipeline.py --force-audio
# Skip audio validation (use existing audio as-is)
python scripts/pipeline.py --skip-validation
# Custom validation threshold
python scripts/pipeline.py --threshold 0.7 --max-retries 3
The file sets lang_code to zh and uses a zh-CN voice, which imposes a specific language/locale choice. There is no indication here that users can opt in to other languages or that the locale restriction is justified as region-specific.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
Returns dict with keys: width, height, r_frame_rate, pix_fmt, codec_name.
"""
try:
result = subprocess.run(
[
"ffprobe", "-v", "quiet",
"-select_streams", "v:0",
The concat-demuxer file list is built by embedding absolute paths inside single quotes without escaping embedded quote characters. A crafted filename containing quote characters or concat-directive syntax could break the file list format and potentially cause FFmpeg to read unintended files or behave unexpectedly, which is risky in a pipeline that may process attacker-controlled filenames.
]
print(f"[INFO] Running: {' '.join(cmd[:6])} ... {output_path.name}")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
"-movflags", "+faststart+negative_cts_offsets",
str(output_path),
]
fix_result = subprocess.run(fix_cmd, capture_output=True, text=True)
tmp_output.unlink(missing_ok=True)
if fix_result.returncode != 0:
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
]
print(f"[INFO] Running ffmpeg with concat filter...")
result = subprocess.run(
cmd,
capture_output=True,
text=True,
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
# Get duration via ffprobe
try:
probe = subprocess.run(
["ffprobe", "-v", "quiet",
"-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1",
Edge TTS mode sends slide text to Microsoft's online service, but the pipeline does not provide an explicit privacy disclosure or confirmation before transmitting potentially sensitive presentation content off-host. In a slide-to-video skill, speaker notes often contain proprietary or internal material, so silent network exfiltration to a third party is materially risky.
HTTP TTS mode POSTs full slide text to a network endpoint and defaults to plain HTTP on localhost, with no transport-security requirements or explicit disclosure to the operator that content is leaving the current process boundary. If the URL is changed to a remote host or traffic is intercepted, sensitive presentation text may be exposed.
The STT validation call forces language="zh", which imposes a specific language/locale behavior regardless of user input or configuration. This is a natural-language policy concern because the file does not offer a language choice or explain why Chinese-only transcription is required.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
threshold: float = DEFAULT_THRESHOLD,
max_retries: int = DEFAULT_MAX_RETRIES,
force: bool = False,
skip_validation: bool = False,
) -> bool:
"""
Stage 1: Big loop — for each slide, generate audio then immediately validate.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
threshold: float = DEFAULT_THRESHOLD,
max_retries: int = DEFAULT_MAX_RETRIES,
force: bool = False,
skip_validation: bool = False,
) -> bool:
"""
Stage 1: Big loop — for each slide, generate audio then immediately validate.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
threshold: float = DEFAULT_THRESHOLD,
max_retries: int = DEFAULT_MAX_RETRIES,
force: bool = False,
skip_validation: bool = False,
) -> bool:
"""
Stage 1: Big loop — for each slide, generate audio then immediately validate.
Tool defaults are unsafe or overly permissive (e.g. disabled TLS verification, no authentication, world-writable permissions). Unsafe defaults widen the attack surface.
threshold: float = DEFAULT_THRESHOLD,
max_retries: int = DEFAULT_MAX_RETRIES,
force: bool = False,
skip_validation: bool = False,
) -> bool:
"""
Stage 1: Big loop — for each slide, generate audio then immediately validate.
This branch explicitly accepts audio as valid without transcription or similarity checks whenever skip_validation is set. In this skill, which converts presentation content into publishable videos, accepting unverified audio weakens integrity guarantees and can conceal content substitution or accidental mismatches.
raise
# Step B: Skip validation if requested
if skip_validation:
audio_dur = result.get("duration_seconds", 0) if need_generate else get_audio_duration(str(audio_path))
flush_print(f" ⏭️ Validation skipped, audio accepted ({audio_dur:.1f}s)")
passed = True
No suspicious patterns detected.