T09 · Insecure Skill Coding Practices
- Location
scripts/step2_edit_video.py:108- Finding
Shell Command Injection in FFmpeg Transition Assembly
- Content
View full analysis
- Remediation
View remediation
Security audit
Security checks for vulnerabilities and agentic risk
This video-making skill is mostly aligned with its purpose, but its scripts can run unintended local commands through unsafe config and FFmpeg handling.
Review before installing. Only run config.py files you trust, avoid untrusted or unusual path and segment names, and prefer running the scripts in an isolated project environment. Pin dependencies where possible, and do not send sensitive or unpublished narration text through edge-tts unless you are comfortable with that external TTS data flow.
scripts/step2_edit_video.py:108Shell Command Injection in FFmpeg Transition Assembly
SKILL.md:18Unpinned Third-Party Dependency Installation
This is a substantive behavior mismatch because the declared skill describes a constrained media workflow, but the referenced implementation reportedly imports and executes a local Python config via importlib. Loading attacker-controlled local config as code can lead to arbitrary code execution during a seemingly harmless content-preparation step, and writing outside a clearly bounded output path further increases the risk of unintended file modification.
xfade_concat builds a shell command string from clip_paths and output_path, then executes it with os.system. Because those values can derive from config, workspace paths, or filenames, an attacker who controls a path containing shell metacharacters or quotes could break out of the intended command and execute arbitrary OS commands.
cmd = cmd.replace(f"OFFSET{i}", f"{offset_val:.2f}")
print(f" xfade chain: {n} clips, {n-1} transitions")
ret = os.system(cmd)
if ret != 0:
print(" xfade failed -> fallback to simple concat")
return concat_simple(clip_paths, output_path, output_dir)
The README specifies a Chinese default voice and only lists Chinese voice options, while the document itself presents the tool as generally suitable for social-media video creation rather than clearly limiting it to a Chinese-only use case. This can violate language/locale policy because it imposes a specific locale without user opt-in or an explicit documented constraint.
The skill instructs use of shell, file reads, and file writes but does not declare any tool scope or permissions boundaries. That omission weakens reviewability and least-privilege controls, making it easier for an agent/runtime to grant broader capabilities than the task actually needs, especially given direct execution of Python and FFmpeg workflows against user-supplied paths.
The instructions set the default voice to zh-CN-YunxiNeural and elsewhere assume Chinese subtitle rendering via SimHei, which imposes a specific locale/language preference. The policy allows locale constraints when users are given a choice or the constraint is clearly justified as region-specific; this file does not clearly offer such an opt-in language selection as part of the skill's general behavior.
The script sends narration text to edge-tts, which involves transmitting user-provided content to an external service, but it does so without explicit disclosure or consent in the script flow. In a video-production context this may be expected, yet it still creates privacy and confidentiality risk if scripts contain sensitive or unpublished material.
The script dynamically loads and executes a user-supplied Python config file with spec.loader.exec_module(cfg), which grants arbitrary code execution at startup. That is much broader than needed for configuration of paths and segment metadata, and an attacker who can influence the config file or its path can run code on the host under the script's privileges.
Beyond the core code-execution issue, the script does not clearly disclose to users that providing a config.py will execute arbitrary Python code. This increases risk because users may reasonably treat a config file as inert data, making social engineering or supply-chain abuse of shared project configs more likely.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def get_duration(path):
r = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", str(path)],
capture_output=True, text=True
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def get_duration(path):
r = subprocess.run(
["ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", str(path)],
capture_output=True, text=True
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def get_resolution(path):
r = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height",
"-of", "default=noprint_wrappers=1", str(path)],
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def get_resolution(path):
r = subprocess.run(
["ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height",
"-of", "default=noprint_wrappers=1", str(path)],
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
def ffmpeg_run(args):
"""Run ffmpeg command with args list (no shell escaping issues)."""
cmd = ["ffmpeg", "-y"] + args
r = subprocess.run(cmd, capture_output=True, text=True, encoding='utf-8', errors='replace')
if r.returncode != 0:
print(f" FFmpeg error: {(r.stderr or '')[-300:]}")
return r.returncode == 0
The code comments and docstrings claim FFmpeg is invoked without shell escaping issues, but xfade_concat contradicts that by constructing a shell command and using os.system. This mismatch is dangerous because reviewers may trust the safety claim and overlook the command-injection surface created by the shell-executed path handling.
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
"-c:v", "libx264", "-preset", "fast", "-crf", "20",
"-pix_fmt", "yuv420p", output_path]
result = subprocess.run(cmd, capture_output=True, text=True, encoding="utf-8", errors="replace")
if result.returncode != 0:
print(f" FFmpeg error: {(result.stderr or '')[-500:]}")
shutil.copy2(input_path, output_path)
subprocess module calls execute external commands. Without careful input validation, this enables command injection.
cmd2 = ["ffmpeg", "-y", "-i", input_path, "-vf", vf2,
"-c:v", "libx264", "-preset", "fast", "-crf", "20",
"-pix_fmt", "yuv420p", output_path]
result2 = subprocess.run(cmd2, capture_output=True, text=True, encoding="utf-8", errors="replace")
out_size2 = os.path.getsize(output_path)
ok = out_size2 != in_size
if ok:
The code defaults VOICE to "zh-CN-YunxiNeural", which hard-codes a Chinese locale for narration generation when the config does not override it. This imposes a language/locale choice on users without offering selection or documenting an opt-in, which matches the language/locale policy violation criteria.
The config sets VOICE = "zh-CN-YunxiNeural", which forces a Chinese locale by default. The surrounding comments list only Chinese voices and do not mention that users may choose another language/locale or why Chinese is required, which is a natural-language locale policy concern.
The module documentation states that OUTPUT_DIR is the path to the output folder, implying generated artifacts are placed there, and the script description says it exports subtitle_review.md as part of step output. However, the actual write target is review_path = VIDEO_DIR / "subtitle_review.md", which places the generated review artifact alongside source videos instead of the output directory.
This docstring explicitly states the script's approach is to use subprocess without a shell to avoid Windows escaping problems entirely. However, later/elsewhere in the same script, xfade_concat executes FFmpeg through os.system with a composed shell command, so the documented claim of avoiding shell execution entirely is not true for the script's actual behavior.
No suspicious patterns detected.