Back to skill

Security audit

English Learning Animation

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent local video-production skill, but editable project files can direct some scripts to write or inspect files outside the intended project folder.

Review before installing. Use this only with lesson projects and manifests you trust, preferably in a sandboxed working directory. Until path validation is fixed, keep voice-manifest.json output_dir and segment file values project-relative, avoid absolute paths or '..', and do not run the validators on untrusted script.json files. No evidence of hidden exfiltration or persistence was found.

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 (2)

T09 · Insecure Skill Coding Practices

Warning
Location
scripts/generate_qwen3_voices.py:28
Finding

Manifest-Controlled Path Traversal Allows Files to Be Written Outside the Project

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_qwen3_voices.py, lines 28–32 and 74–75; related incomplete validation in scripts/validate_contract.py, lines 9–15
Vulnerability Type: Arbitrary file write through insufficient path validation
Risk Level: Medium

Vulnerable Code

python
data = json.loads(args.manifest.read_text(encoding="utf-8"))
out_dir = Path(data["output_dir"])
if not out_dir.is_absolute():
    out_dir = args.manifest.resolve().parent / out_dir
out_dir.mkdir(parents=True, exist_ok=True)
python
target = out_dir / segment["file"]
sf.write(target, wavs[0], sample_rate)

The related contract validator only validates the directory and does not independently validate the segment filename:

python
def normalized_output(output_dir: str, filename: str) -> str:
    directory = PurePosixPath(output_dir.replace("\\", "/"))
    if directory.is_absolute() or ".." in directory.parts:
        raise ValueError("output_dir must be a safe project-relative path")
    if directory.parts and directory.parts[0] == "public":
        directory = PurePosixPath(*directory.parts[1:])
    return str(directory / filename)

Technical Analysis

The voice manifest is treated as trusted when output paths are constructed. An absolute output_dir is explicitly accepted. A relative directory containing traversal components is also accepted by the generator because the resulting path is not resolved and checked against the project root.

The segment["file"] value is likewise not constrained to a simple filename. It may contain ../ components or be an absolute path. In Python, joining a Path with an absolute second operand discards the original base path. Consequently, the final target can point anywhere writable by the process.

Although validate_contract.py rejects an absolute or traversing output_dir, it does not validate filename. More importantly, the d ...[truncated 1370 chars]

Remediation
View remediation

Remediation Suggestions

  • Define the manifest directory or project root as the only permitted output boundary.
  • Reject absolute output_dir and file values.
  • Reject empty filenames, . and .. components, path separators in segment filenames, and filenames not ending in .wav.
  • Resolve the final path and verify containment before creating directories or writing data:
    python
    project_root = args.manifest.resolve().parent
    output_dir = (project_root / str(data["output_dir"])).resolve()
    
    if not output_dir.is_relative_to(project_root):
        raise SystemExit("output_dir must remain inside the project")
    
    filename = Path(str(segment["file"]))
    if (
        filename.is_absolute()
        or filename.name != str(filename)
        or filename.suffix.lower() != ".wav"
    ):
        raise SystemExit("segment file must be a simple .wav filename")
    
    target = (output_dir / filename).resolve()
    if not target.is_relative_to(output_dir):
        raise SystemExit("segment output escapes output_dir")
    
  • Apply the same validation inside both the generator and contract validator. Validation in a separate command must not be the only security boundary.
  • Refuse to replace existing files by default. Require an explicit --overwrite option where replacement is intended.
  • Consider writing to a temporary file within the validated output directory and atomically renaming it after successful generation.

T09 · Insecure Skill Coding Practices

Note
Location
scripts/validate_layers.py:64
Finding

Project-Controlled Asset Paths Can Escape the Public Directory

Content
View full analysis

Vulnerability Details

File Location: scripts/validate_layers.py, lines 64–81; scripts/validate_timeline.py, lines 103–104
Vulnerability Type: Path traversal and unintended local file processing
Risk Level: Low

Vulnerable Code

Background and cutout paths from script.json are joined to the public directory without a containment check:

python
for relative in sorted(backgrounds):
    path = args.public / relative
    if not path.exists():
        errors.append(f"missing background: {path}")
        continue
    try:
        with Image.open(path) as image:
            image.verify()
    except Exception as exc:
        errors.append(f"invalid background image {path}: {exc}")

for relative in sorted(layers):
    path = args.public / relative
    if not path.exists():
        errors.append(f"missing cutout: {path}")
        continue
    try:
        with Image.open(path) as image:
            has_alpha = image.mode in {"RGBA", "LA"} or (
                image.mode == "P" and "transparency" in image.info
            )

Narration output paths are handled in the same manner before being passed to FFprobe:

python
audio = args.public / str(item["output"])
if not audio.exists():
    errors.append(f"missing audio: {audio}")
    continue
actual = math.ceil(seconds(audio) * fps)

Technical Analysis

The background, layer src, and narration output values originate in the editable script.json. Joining these values with args.public does not ensure that the result stays under that directory.

A path containing ../ can traverse to an ancestor directory. If the supplied value is absolute, Python's path-joining behavior discards args.public entirely. The validators then open the selected file using Pillow or pass it to FFprobe.

This is primarily an unintended local file access and parser-exposure issue. The scripts do not transmit file contents o ...[truncated 1593 chars]

Remediation
View remediation

Remediation Suggestions

  • Resolve the public directory once and require every asset path to remain beneath it:
    python
    public_root = args.public.expanduser().resolve()
    
    def public_asset(value: str) -> Path:
        relative = Path(value)
        if relative.is_absolute() or ".." in relative.parts:
            raise ValueError("asset path must be project-relative")
    
        candidate = (public_root / relative).resolve()
        if not candidate.is_relative_to(public_root):
            raise ValueError("asset path escapes the public directory")
        return candidate
    
  • Use the same centralized helper for backgrounds, cutout layers, narration audio, and any future project-controlled asset references.
  • Reject absolute paths and traversal components before invoking Pillow, FFprobe, FFmpeg, or Remotion.
  • If symbolic links are not required, reject assets that resolve through symlinks or require the resolved destination to remain inside public.
  • Validate expected file extensions and formats before invoking native decoders.
  • Add regression tests covering absolute paths, ../ traversal, nested traversal, platform-specific separators, and symlinks pointing outside the project.
  • Keep Pillow and FFmpeg patched and consider resource limits for processing untrusted media.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Output HandlingUnvalidated Output Injection, Cross-Context Output, Unbounded Output
  • Rogue AgentSelf-Modification, Session Persistence
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (41)

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description describes a full content-creation skill for English-learning animated videos with audiovisual editing and voice character work. The supplied code chunk does not implement any of that functionality. It only parses an optional cache directory, looks under the local Hugging Face cache for the Qwen3-TTS VoiceDesign model snapshots folder, and prints the most recently modified snapshot path. While this is tangentially related to Qwen3-TTS VoiceDesign, it is only a support utility for locating a model checkpoint, not a tool for creating or revising ESL animation videos. Therefore the code's actual behavior is materially narrower and different from the declared primary purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
92% confidence
Finding

The declared description presents a broad animation-video creation/editing skill covering visuals, covers, subtitles, motion, and audiovisual synchronization. The supplied code chunk only performs Qwen3-TTS voice generation and writes WAV files. While distinct character voice generation is one component mentioned in the description, the actual code does not implement the broader primary purpose of creating or revising animated English-learning videos. This is a material description/behavior mismatch because the code’s actual scope is much narrower and audio-only.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents a content-creation skill for producing or refining English-learning animation videos and related audiovisual elements. The supplied code does not perform any video creation, editing, voice generation, subtitle work, cover creation, timing synchronization, or character-specific animation logic. Its sole function is to scaffold a project directory by copying a starter template and printing setup guidance. While project initialization could be a supporting development step for such a workflow, this chunk's primary purpose is materially different from the declared end-user capability, so it is a mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description frames the skill as a content-creation or editing tool for animated ESL videos. However, the supplied code only validates consistency between two existing JSON artifacts: a voice manifest and a timeline. Its purpose is narrow contract checking, not media generation or revision. While this validation could support an animation pipeline, it is only an internal QA/helper step and does not itself perform the described creative or editing functions. Therefore the code's actual behavior is materially narrower and different from the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The description promises a content-creation/editing skill for English-learning animated videos with voice, subtitles, cover, motion, and audiovisual synchronization features. The actual code does none of that. It only validates visual asset files referenced in script.json, specifically checking backgrounds and transparent cutout layers for existence and alpha-quality heuristics. While such validation could be a supporting utility within a video pipeline, this chunk’s primary purpose is materially different from the declared user-facing behavior and omits the core claimed capabilities entirely. Therefore this is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

The declared description presents a content-creation/editing skill for producing or improving animated ESL videos and related creative elements like voices, subtitles, cover, motion, and synchronization. The supplied code does not create or revise videos or any of those assets. Instead, it validates an existing project structure and optionally reviews a rendered video by invoking other scripts. This is a materially different primary purpose: quality-gate validation/orchestration rather than animation or lesson creation/editing. The code’s behavior is related to the same domain of video projects, but its actual capability is an internal validation pipeline that is not disclosed in the description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared purpose focuses on generating or revising animated English-learning videos with specific creative and audiovisual features. The supplied code does not create, revise, render, animate, synthesize voices, edit subtitles, or synchronize audio/visual timing. Instead, it performs pre-render validation of script semantics and some static checks on renderer source code. While such validation could support a video-production pipeline, this chunk’s primary function is materially different from the declared end-user capability, so it should be flagged as a mismatch.

Content

No source excerpt is available for this finding.

Unvalidated Output Injection

High
Category
Output Handling
Confidence
95% confidence
Finding

Model output is used without validation or sanitization. Unvalidated output injected into downstream contexts (SQL, shell, HTML) enables injection attacks and arbitrary code execution.

Content

Scanner excerpt · scripts/extract_review_frames.py (reported line 49)May include surrounding context.

python
args.output_dir.mkdir(parents=True, exist_ok=True)
    for label, timestamp in frames:
        output = args.output_dir / f"{label}.png"
        subprocess.run(
            [
                "ffmpeg",
                "-y",

Self-Modification

High
Category
Rogue Agent
Confidence
85% confidence
Finding

Skill modifies its own code, configuration, or behavior at runtime. Self-modification enables an agent to escalate privileges, disable safety constraints, or install persistent backdoors.

Content

Scanner excerpt · scripts/init_project.py (reported line 23)May include surrounding context.

python
raise SystemExit(f"starter not found: {source}")
    if target.exists() and any(target.iterdir()):
        raise SystemExit(
            f"target must be new or empty; refusing to overwrite existing files: {target}"
        )

    target.mkdir(parents=True, exist_ok=True)

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The description states the skill creates videos with 'English-only on-video copy,' which imposes a language constraint as a default behavior. The README does not offer an opt-in choice for language or explain a justified region-specific requirement, so this appears to violate the language/locale policy criteria.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

This line says the generated output includes an 'English-only video surface,' covering captions and practice cards. Because no alternative language option or explicit user opt-in is described, the natural-language documentation enforces a specific locale choice.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The phrase 'English-only on-video text by default' explicitly sets a default language restriction. Under the stated policy, forcing a specific language without user opt-in or documented justification is a reportable natural-language policy concern.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding

The skill instructs use of shell commands and local scripts that can read, write, and inspect the filesystem, but it does not declare any explicit tool scope or permissions boundary. That increases the chance an agent will run the skill with broader-than-necessary capabilities, enabling unintended file access or command execution if the surrounding platform does not enforce least privilege elsewhere.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The instruction 'Keep on-video language English-only for immersion' imposes a specific language policy on outputs. The file does not present this as an optional user preference or clearly justified region-specific requirement, so it constitutes a language/locale policy violation under the stated rules.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The phrase "English-only cover" and the overall instruction to create an "English-learning animation" impose a language constraint in the natural-language specification. The file does not offer user opt-in for this locale/language restriction or document a policy justification for forcing English-only output.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

The comment states that this is an 'English-immersion lesson' and that Chinese translations should never appear in in-video dialogue. This is a natural-language locale policy embedded in the code and does not offer user choice or indicate a clearly justified region-specific requirement.

Content

No source excerpt is available for this finding.

Rp1

Medium
Category
MCP Rug Pull
Confidence
70% confidence
Finding

npx commands without a version suffix (e.g. @1.0.0) create a rug-pull risk if the upstream server is compromised and publishes a malicious update.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The requirement 'Cover is 2–3 seconds, English-only' imposes a specific language choice as a hard policy, rather than offering the user a language option. This matches the language/locale policy violation category because it forces a language without opt-in.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
97% confidence
Finding

The instruction 'Video surface contains no Chinese unless the user explicitly requests bilingual on-video subtitles' restricts one language by default without explaining a compliance or regional necessity. This is a natural-language policy issue because it enforces a locale/language constraint instead of presenting a neutral user choice.

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 · scripts/extract_review_frames.py (reported line 49)May include surrounding context.

python
args.output_dir.mkdir(parents=True, exist_ok=True)
    for label, timestamp in frames:
        output = args.output_dir / f"{label}.png"
        subprocess.run(
            [
                "ffmpeg",
                "-y",

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The call to generate_voice_design hard-codes language="English", which imposes a specific language regardless of user input or manifest content. This is a natural-language policy concern because the skill does not offer an opt-in, override, or documented reason for restricting output to English.

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 · scripts/validate_lesson.py (reported line 11)May include surrounding context.

python
def duration(path: Path) -> float:
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(path)], capture_output=True, text=True, check=True)
    return float(result.stdout.strip())

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 · scripts/validate_timeline.py (reported line 12)May include surrounding context.

python
def duration(path: Path) -> float:
    result = subprocess.run(["ffprobe", "-v", "error", "-show_entries", "format=duration", "-of", "default=noprint_wrappers=1:nokey=1", str(path)], capture_output=True, text=True, check=True)
    return float(result.stdout.strip())

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
96% confidence
Finding

The file’s docstring and validation logic explicitly require English-only content by rejecting titles and lesson text containing CJK characters. This is a natural-language policy concern because it forces a specific language/locale without any visible opt-in or documented regional/compliance justification in the file.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Medium
Category
Not specified by scanner
Confidence
98% confidence
Finding

The validator rejects segment text when it contains CJK characters, effectively enforcing an English-only policy on lesson content. No nearby comments, prompts, or configuration indicate that users can choose another language or that the restriction is required for a documented regional purpose.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.