Back to skill

Security audit

Youtube Factory

Security checks for vulnerabilities and agentic risk

Overview

This video-generation skill is mostly purpose-aligned, but it needs review because a custom output name can make it write files outside the advertised video output folder.

Review this before installing if you will let agents choose command arguments. Use a non-sensitive topic, keep the Pexels key limited to Pexels, run it in an isolated environment, avoid custom --output values unless they are simple directory names, and expect generated files to remain on disk under your account.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • 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
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
youtube_factory.py:362
Finding

Unrestricted Output Path Allows Filesystem Writes Outside the Intended Directory

Content
View full analysis

Vulnerability Details

File Location: youtube_factory.py, lines 362–365
Vulnerability Type: Improper path validation and path traversal
Risk Level: High

python
if output_name is None:
    output_name = re.sub(r'[^a-z0-9-]', '', topic.lower().replace(" ", "-"))[:50]
video_dir = OUTPUT_DIR / output_name
video_dir.mkdir(parents=True, exist_ok=True)

Technical Analysis

The automatically generated output name is sanitized only when output_name is None. A caller-supplied --output value is used directly in the path expression without validation.

Python's pathlib permits absolute path components and parent-directory components such as ... If output_name is absolute, combining it with OUTPUT_DIR results in the absolute path taking precedence. If it is relative and contains traversal components, the resulting path can escape the intended output directory.

The generation process subsequently writes predictable filenames such as script.md, voiceover.mp3, metadata.json, and several video or image files into this directory. Some operations, including Path.write_text() and FFmpeg invocations using -y, can overwrite existing files.

Attack Path

  1. An attacker influences the --output argument passed to the Skill, directly or through an agent-generated invocation.
  2. The attacker supplies an absolute path or traversal value, such as --output /tmp/target or --output ../../target.
  3. The value bypasses the sanitization branch because output_name is not None.
  4. video_dir.mkdir() creates or selects a directory outside OUTPUT_DIR.
  5. The Skill writes generated artifacts with predictable names into the attacker-selected location.
  6. If matching files already exist and the invoking account has write permission, those files may be overwritten.

Impact Assessment

Exploitation permits file creation and overwrite outside the declared ~/Videos/OpenClaw output area. Th ...[truncated 466 chars]

Remediation
View remediation

Remediation Suggestions

  1. Reject absolute output paths and any path containing ...
  2. Resolve both the configured base directory and the requested destination, then verify that the destination remains beneath the base directory.
  3. Apply a strict allowlist to caller-provided directory names, not only automatically generated names.
  4. Refuse to overwrite existing output directories or files unless the user explicitly authorizes replacement.
  5. Use a newly created, uniquely named directory for each generation job where practical.
  6. Treat all agent-generated command arguments as untrusted input.

Example hardening:

python
base_dir = OUTPUT_DIR.resolve()

if output_name is None:
    output_name = re.sub(
        r"[^a-z0-9-]", "", topic.lower().replace(" ", "-")
    )[:50]
elif not re.fullmatch(r"[A-Za-z0-9_-]{1,64}", output_name):
    raise ValueError("Invalid output directory name")

video_dir = (base_dir / output_name).resolve()
if video_dir.parent != base_dir:
    raise ValueError("Output directory must be directly under OUTPUT_DIR")

video_dir.mkdir(parents=False, exist_ok=False)

T08 · Insecure Dependencies

Warning
Location
SKILL.md:101
Finding

Unpinned Third-Party Installation Instructions Create Supply-Chain Exposure

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 101–104
Vulnerability Type: Unpinned and non-reproducible dependencies
Risk Level: Medium

bash
# Install dependencies
brew install ffmpeg
pip install edge-tts pillow python-dotenv requests

Technical Analysis

The setup instructions install third-party packages without pinned versions, integrity hashes, or a lockfile. Consequently, the installed components are determined by the state of the package repositories at installation time rather than by the versions reviewed with this Skill.

Python package installation can execute package build and installation logic. Homebrew formulas and their transitive dependencies are also mutable over time. A compromised upstream release, package repository, maintainer account, or transitive dependency could therefore introduce code not represented by the audited project.

The instructions also install python-dotenv, although the implementation shown in the audit does not import or use that package. This unnecessarily increases the dependency and attack surface.

No evidence was found that the currently named packages are malicious. The finding concerns the absence of reproducible dependency controls and the resulting exposure to future or transient supply-chain compromise.

Attack Path

  1. A user follows the setup instructions from SKILL.md.
  2. Package managers resolve the latest releases and transitive dependencies available at that time.
  3. If an upstream package, release channel, maintainer account, or dependency has been compromised, the package manager retrieves the altered component.
  4. Installation or later import and execution runs code that was not included in the audited Skill.
  5. That code executes with the privileges of the user performing the installation or running the Skill.

Impact Assessment

Successful exploitation through a compromised dependency could allow arbitrary code ex ...[truncated 464 chars]

Remediation
View remediation

Remediation Suggestions

  1. Pin every Python dependency to a reviewed version.
  2. Provide a lockfile or requirements file containing cryptographic hashes, such as one generated with pip-tools.
  3. Pin or otherwise document tested FFmpeg and Edge TTS versions.
  4. Install packages only from documented, trusted repositories.
  5. Review and pin transitive dependencies where the package-management workflow permits it.
  6. Remove python-dotenv from the setup instructions unless it becomes an actual runtime requirement.
  7. Run dependency vulnerability and provenance checks as part of release preparation.
  8. Prefer an isolated virtual environment instead of installing Python packages globally.

Example Python installation pattern:

bash
python -m venv .venv
. .venv/bin/activate
python -m pip install --require-hashes -r requirements.txt

The accompanying requirements.txt should contain exact versions and verified hashes for all direct and transitive dependencies.

Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
Findings (25)

Tainted flow: 'PEXELS_API_KEY' from os.getenv (line 45, credential/environment) → requests.get (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · youtube_factory.py (reported line 99)May include surrounding context.

python
print("WARNING: No Pexels API key. Get one free at https://www.pexels.com/api/")
        return []

    response = requests.get(
        "https://api.pexels.com/videos/search",
        headers={"Authorization": PEXELS_API_KEY},
        params={"query": query, "per_page": count, "orientation": orientation, "size": "medium"},

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The documentation claims the skill is self-contained and uses no external modules, yet it depends on external Python packages, CLI tools, network services, and extensive local file output. This mismatch is security-relevant because it can mislead users into approving execution under false assumptions about supply-chain exposure, data egress, and local system impact.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
96% confidence
Finding

The skill advertises capabilities that require environment access, filesystem writes, network use, and shell execution, but it does not declare any explicit tool scope or permissions. This creates a transparency and least-privilege failure: users and hosts cannot accurately assess or constrain what the skill is allowed to do before execution.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

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.

Content

Scanner excerpt · SKILL.md (reported line 23)May include surrounding context.

md
Turn any topic into a publish-ready YouTube video:

1. **Script Generation** - Uses your LLM to write engaging scripts
2. **Voiceover** - Free Microsoft Edge TTS (natural-sounding voices)
3. **Stock Footage** - Auto-fetches relevant B-roll from Pexels (free)
4. **Video Assembly** - FFmpeg combines everything seamlessly

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The workflow sends user-provided topics or prompts to external services for stock media lookup and text-to-speech, but the documentation does not include a privacy warning. Users may unknowingly submit sensitive project ideas, names, or proprietary content to third-party providers, causing unintended data disclosure.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
90% confidence
Finding

The available voice list contains only English locales (en-US, en-GB, en-AU), and the examples and defaults are all English, with no indication that users can choose another language or that the skill is intentionally English-only. This is a natural-language locale policy concern because the skill constrains output language without user opt-in or justification.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The module docstring says the skill is 'self-contained, no external modules', yet the code imports and uses the requests library, calls the Pexels API over the network, invokes external executables like edge-tts/ffmpeg/ffprobe, and optionally imports Pillow. This is a semantic mismatch between the advertised packaging/operation model and the actual implementation.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

At L084 the code defaults text-to-speech to en-US-ChristopherNeural, and the CLI also defaults to the same locale-specific voice later. This is a natural-language policy concern because the skill implicitly forces a particular language/locale rather than asking for user preference or clearly justifying the restriction.

Content

No source excerpt is available for this finding.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 85)May include surrounding context.

python
rate: str = "+0%", pitch: str = "+0Hz") -> str:
    """Convert text to speech using Edge TTS (free Microsoft voices)"""
    voice = voice or os.getenv("DEFAULT_VOICE", "en-US-ChristopherNeural")
    subprocess.run([
        "edge-tts", "--voice", voice, "--rate", rate,
        "--pitch", pitch, "--text", text, "--write-media", output_path
    ], capture_output=True)

Tainted flow: 'voice' from os.getenv (line 84, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · youtube_factory.py (reported line 85)May include surrounding context.

python
rate: str = "+0%", pitch: str = "+0Hz") -> str:
    """Convert text to speech using Edge TTS (free Microsoft voices)"""
    voice = voice or os.getenv("DEFAULT_VOICE", "en-US-ChristopherNeural")
    subprocess.run([
        "edge-tts", "--voice", voice, "--rate", rate,
        "--pitch", pitch, "--text", text, "--write-media", output_path
    ], capture_output=True)

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · youtube_factory.py (reported line 100)May include surrounding context.

python
return []

    response = requests.get(
        "https://api.pexels.com/videos/search",
        headers={"Authorization": PEXELS_API_KEY},
        params={"query": query, "per_page": count, "orientation": orientation, "size": "medium"},
        timeout=30,

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 140)May include surrounding context.

python
# Detect orientation from first clip
    target_w, target_h = 1080, 1920
    probe = subprocess.run([
        "ffprobe", "-v", "quiet", "-select_streams", "v:0",
        "-show_entries", "stream=width,height", "-of", "csv=p=0",
        video_paths[0]

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 156)May include surrounding context.

python
scaled = []
    for i, path in enumerate(video_paths):
        sp = secure_tempfile(suffix=f"_s{i}.mp4")
        subprocess.run([
            "ffmpeg", "-y", "-i", path,
            "-vf", f"scale={target_w}:{target_h}:force_original_aspect_ratio=decrease,pad={target_w}:{target_h}:(ow-iw)/2:(oh-ih)/2:black",
            "-c:v", "libx264", "-preset", "fast", "-crf", "23", "-c:a", "aac", sp

Tainted flow: 'sp' from os.getenv (line 477, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · youtube_factory.py (reported line 156)May include surrounding context.

python
scaled = []
    for i, path in enumerate(video_paths):
        sp = secure_tempfile(suffix=f"_s{i}.mp4")
        subprocess.run([
            "ffmpeg", "-y", "-i", path,
            "-vf", f"scale={target_w}:{target_h}:force_original_aspect_ratio=decrease,pad={target_w}:{target_h}:(ow-iw)/2:(oh-ih)/2:black",
            "-c:v", "libx264", "-preset", "fast", "-crf", "23", "-c:a", "aac", sp

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 173)May include surrounding context.

python
"-map", "[outv]", "-c:v", "libx264", "-preset", "fast", "-crf", "23",
        output_path
    ])
    subprocess.run(cmd, capture_output=True)

    for p in scaled:
        try:

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 185)May include surrounding context.

python
def add_audio_to_video(video_path: str, audio_path: str, output_path: str) -> str:
    """Add voiceover to video"""
    probe = subprocess.run([
        "ffprobe", "-v", "quiet", "-select_streams", "a",
        "-show_entries", "stream=codec_type", "-of", "csv=p=0", video_path
    ], capture_output=True, text=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 191)May include surrounding context.

python
], capture_output=True, text=True)

    if probe.stdout.strip():
        subprocess.run([
            "ffmpeg", "-y", "-i", video_path, "-i", audio_path,
            "-filter_complex", "[0:a]volume=0.1[v];[1:a]volume=1.0[a];[v][a]amix=inputs=2:duration=longest",
            "-c:v", "copy", output_path

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 197)May include surrounding context.

python
"-c:v", "copy", output_path
        ], capture_output=True)
    else:
        subprocess.run([
            "ffmpeg", "-y", "-i", video_path, "-i", audio_path,
            "-c:v", "copy", "-c:a", "aac", "-map", "0:v:0", "-map", "1:a:0",
            output_path

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
71% confidence
Finding

The ffmpeg call embeds a temporary subtitle path directly into the filter expression as ass={ass_file}. ffmpeg filter parsing has its own escaping rules, so special characters in a path can break parsing or alter filter behavior; on many systems temp paths are environment-influenced, making this a real injection/confusion surface even though Python is not using a shell.

Content

Scanner excerpt · youtube_factory.py (reported line 238)May include surrounding context.

python
with open(ass_file, "w") as f:
        f.write(content)
    subprocess.run([
        "ffmpeg", "-y", "-i", video_path, "-vf", f"ass={ass_file}",
        "-c:v", "libx264", "-c:a", "copy", output_path
    ], capture_output=True)

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 259)May include surrounding context.

python
def resize_for_shorts(input_path: str, output_path: str) -> str:
    """Convert to vertical 9:16 for Shorts"""
    subprocess.run([
        "ffmpeg", "-y", "-i", input_path,
        "-vf", "scale=1080:1920:force_original_aspect_ratio=decrease,pad=1080:1920:(ow-iw)/2:(oh-ih)/2:black",
        "-c:v", "libx264", "-c:a", "aac", output_path

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · youtube_factory.py (reported line 269)May include surrounding context.

python
def extract_frame(video_path: str, output_path: str, timestamp: float = 5.0) -> str:
    """Extract a frame for thumbnail"""
    subprocess.run([
        "ffmpeg", "-y", "-ss", str(timestamp), "-i", video_path,
        "-vframes", "1", "-q:v", "2", output_path
    ], capture_output=True)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

At L518 the --voice argument defaults to en-US-ChristopherNeural, establishing a locale-specific default in the user interface. Because the skill does not present language selection guidance or an opt-in choice, this can violate language/locale policy expectations.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
87% confidence
Finding

The skill writes several generated artifacts into a directory under the user's home folder, but the documentation does not clearly warn about this side effect before use. Undisclosed persistent writes can surprise users, consume disk space, and leave behind sensitive or unwanted media files.

Content

No source excerpt is available for this finding.

Intent-Code Divergence

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The docstring presents the skill as requiring no costly APIs, implying a largely standalone/free workflow, but the actual stock-footage path depends on a Pexels API key and network access, and voice generation depends on an external CLI/service. While not necessarily expensive, the documentation overstates independence from external services.

Content

No source excerpt is available for this finding.

Missing User Warnings

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

At L045 the skill loads PEXELS_API_KEY, and earlier code reads it from a user config file, but there is no explicit user warning that the skill consumes stored API credentials. For a code-file audit under SQP-2, access to sensitive environment variables should have some visible disclosure unless clearly communicated elsewhere in the skill file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.