Back to skill

Security audit

Scene Video Pipeline

Security checks for vulnerabilities and agentic risk

Overview

This video-production skill is mostly coherent, but one script can write or delete files outside the intended project folder if its narration manifest contains unsafe paths.

Review this skill before installing or running it. Only use it with manifests you trust, and preferably fix the TTS script so audio_file is limited to safe .mp3 filenames under the expected tts/audio directory before running it on important projects.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/generate_tts_kokoro.sh:46
Finding

Arbitrary Filesystem Write and Deletion Through an Unvalidated Manifest Output Path

Content
View full analysis

Vulnerability Details

File Location: scripts/generate_tts_kokoro.sh, lines 46-71
Vulnerability Type: Unrestricted path traversal and arbitrary file overwrite
Risk Level: High

Vulnerable Code

python
for row in rows:
    slide = row['slide'].strip()
    rel_out = row['audio_file'].strip()
    text = row['text'].strip()

    mp3_out = module_dir / 'tts' / rel_out
    wav_out = mp3_out.with_suffix('.wav')
    mp3_out.parent.mkdir(parents=True, exist_ok=True)

    print(f"Generating slide {slide} -> {mp3_out}")

    chunks = []
    for _, _, audio in pipeline(text, voice=voice, speed=speed):
        chunks.append(audio)

    if not chunks:
        raise RuntimeError(f"No audio generated for slide {slide}")

    combined = np.concatenate(chunks)
    sf.write(str(wav_out), combined, 24000)

    subprocess.run([
        'ffmpeg', '-y', '-i', str(wav_out), '-codec:a', 'libmp3lame', '-b:a', '192k', str(mp3_out)
    ], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

    wav_out.unlink(missing_ok=True)

Technical Analysis

The audio_file field is read from tts/manifest.csv and used directly to construct an output path without validation or containment enforcement.

Although the code appears to place output beneath the module's tts directory, pathlib permits the supplied value to escape that directory:

  • A value containing traversal components, such as ../../outside/output.mp3, resolves outside the intended directory.
  • An absolute value, such as /tmp/output.mp3, causes pathlib to discard the preceding module and tts path components.
  • Existing writable files can be overwritten because FFmpeg is invoked with -y.
  • The corresponding path with a .wav suffix is created by sf.write() and subsequently removed with unlink().
  • mkdir(parents=True) can create attacker-selected directory trees wherever the executing account has permission.

...[truncated 2041 chars]

Remediation
View remediation

Remediation Suggestions

Treat audio_file as an untrusted filename rather than an unrestricted path.

  1. Establish a fixed output root such as module_dir / "tts" / "audio".
  2. Reject absolute paths, .. components, empty names, and unexpected extensions.
  3. Resolve the candidate output path and verify that it remains beneath the resolved output root.
  4. Restrict filenames to a conservative allowlist, such as zero-padded numeric scene identifiers followed by .mp3.
  5. Validate the derived WAV path independently before writing or deleting it.
  6. Reject symlinked output directories or use race-resistant filesystem operations where the module may be writable by an untrusted party.
  7. Avoid unconditional overwrite behavior unless explicitly required.

Example containment validation:

python
output_root = (module_dir / 'tts' / 'audio').resolve()
supplied = Path(rel_out)

if supplied.is_absolute() or '..' in supplied.parts:
    raise ValueError(f'Invalid audio output path: {rel_out}')

if supplied.suffix.lower() != '.mp3':
    raise ValueError(f'Audio output must use the .mp3 extension: {rel_out}')

mp3_out = (output_root / supplied).resolve()

try:
    mp3_out.relative_to(output_root)
except ValueError:
    raise ValueError(f'Audio output escapes the output directory: {rel_out}')

wav_out = mp3_out.with_suffix('.wav')

For stronger hardening, derive output filenames solely from a strictly validated slide identifier instead of allowing the manifest to select filesystem paths.

Vulnerability Patterns
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (2)

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
89% confidence
Finding

The skill exposes shell-based execution paths through multiple local scripts and external binaries (ffmpeg, ffprobe, node) but does not declare any explicit tool scope such as permissions or allowed-tools. This weakens policy enforcement and reviewability, increasing the chance that an agent can invoke broader shell capabilities than intended, especially since the skill also permits outbound network access for model downloads.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Medium
Category
Not specified by scanner
Confidence
81% confidence
Finding

The manifest describes producing scene-based videos with Kokoro-based narration generation, but this file's specific role is TTS generation from a manifest. Spawning an external binary is a broader execution capability than the stated text-to-speech function itself and is not explicitly declared in the manifest description for this step.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.