Back to skill

Security audit

narrate-video

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it should be reviewed because it can leave extracted private video frames in predictable shared temporary paths.

Review before installing if you work with private videos or use a shared machine. Prefer changing the workflow to use a unique private temporary directory with cleanup, and confirm the skill should only run for narration or voiceover tasks. The Azure Speech key use is expected, but it can incur cloud usage and sends narration text to Azure TTS.

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

Warning
Location
SKILL.md:55
Finding

Predictable Shared Temporary Paths Expose Extracted Video Frames

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 55–58 and 172–174
Vulnerability Type: Predictable shared temporary files and directories
Risk Level: Medium

Vulnerable Code

bash
mkdir -p /tmp/narration-frames
for t in $(seq 0 3 <duration>); do
    ffmpeg -y -ss $t -i <video> -frames:v 1 -q:v 2 /tmp/narration-frames/frame_${t}s.jpg 2>/dev/null
done

The verification phase uses similarly predictable paths:

bash
for t in <timestamps>; do
    ffmpeg -y -ss $t -i <output> -frames:v 1 -q:v 2 /tmp/verify_${t}s.jpg 2>/dev/null
done

Technical Analysis

The documented workflow stores frames extracted from potentially sensitive videos in fixed locations under the shared /tmp directory. It neither creates a unique, owner-only temporary directory nor removes the generated images after use. The ffmpeg -y option also permits unconditional overwriting of existing destinations.

An attacker with local access can predict the directory and filenames. Depending on host permissions and temporary-directory protections, the attacker may inspect residual frames, interfere with concurrent narration jobs, pre-create malicious filesystem entries, or use symbolic links to redirect writes to another path writable by the victim process.

The first command uses a fixed directory shared by every run, while the verification command writes directly to predictable /tmp/verify_* filenames. No ownership, file-type, or symbolic-link checks are performed before writing.

Attack Path

  1. A local attacker predicts that the Skill will use /tmp/narration-frames/frame_${t}s.jpg or /tmp/verify_${t}s.jpg.
  2. The attacker monitors the shared directory for generated files, or pre-creates predictable entries or symbolic links before the workflow runs.
  3. The user invokes the narration workflow on a private video.
  4. ffmpeg -y writes extracted frames to the predictable locations.
  5. The attacker reads retained frames, corrupts the workflow by creat ...[truncated 800 chars]
Remediation
View remediation

Remediation Suggestions

  • Create a unique temporary directory for each run with mktemp -d.
  • Restrict access with an owner-only umask and directory permissions.
  • Register a cleanup trap so extracted frames are deleted on success, failure, or interruption.
  • Keep verification images inside the same private directory instead of writing predictable files directly under /tmp.
  • Refuse to use an existing temporary path and validate that output destinations are regular files beneath the newly created directory.
  • Avoid following pre-existing symbolic links.

Example hardened pattern:

bash
umask 077
frames_dir="$(mktemp -d "${TMPDIR:-/tmp}/narration-frames.XXXXXX")" || exit 1
trap 'rm -rf -- "$frames_dir"' EXIT HUP INT TERM

for t in $(seq 0 3 <duration>); do
    ffmpeg -n -ss "$t" -i <video> -frames:v 1 -q:v 2 \
        "$frames_dir/frame_${t}s.jpg" 2>/dev/null
done

for t in <timestamps>; do
    ffmpeg -n -ss "$t" -i <output> -frames:v 1 -q:v 2 \
        "$frames_dir/verify_${t}s.jpg" 2>/dev/null
done

Where supported, use secure file-creation APIs that atomically create outputs with exclusive semantics and do not follow symbolic links.

Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (8)

Tp4

High
Category
MCP Tool Poisoning
Confidence
97% confidence
Finding

The declared description presents a full media-generation skill for adding synchronized narration to videos via Azure TTS. The supplied code chunk only checks for the presence of AZURE_SPEECH_KEY and AZURE_SPEECH_REGION in a local env file and reports whether they are set. While this could be a supporting helper for such a skill, the chunk itself has a materially different primary purpose and lacks the core declared functionality. There are no suspicious undeclared capabilities beyond reading a local config file, but the description does not accurately represent what this code actually does.

Content

No source excerpt is available for this finding.

Credential Access

High
Category
Privilege Escalation
Confidence
60% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · scripts/narration_script_template.py (reported line 9)May include surrounding context.

python
Usage:
    1. Fill in VOICE_NAME, INPUT_VIDEO, OUTPUT_VIDEO, and SEGMENTS
    2. Ensure AZURE_SPEECH_KEY is set in environment (or .env file)
    3. Run: python3 narration_script.py
"""

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding

The skill invokes shell commands and environment checks but does not declare an explicit tool scope such as allowed-tools or permissions. In an agent environment, undeclared shell/env access weakens containment and can allow the skill to run with broader capabilities than reviewers or orchestrators expect.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
95% confidence
Finding

The trigger phrases are intentionally broad, including generic requests like adding audio or narration to any video. Over-broad activation can cause the skill to capture unrelated user intents and then execute shell-based media processing steps or credential checks in contexts where the user did not clearly request them.

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 9)May include surrounding context.

md
# Video Narration

Add professional voiceover to a video. Analyze the video, write or refine a timed script, generate speech via Azure TTS, and merge — producing a narrated video where audio and visuals stay in sync.

**Input**: $ARGUMENTS

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/narration_script_template.py (reported line 58)May include surrounding context.

python
def get_audio_duration(path):
    """Get duration of an audio file in seconds via ffprobe."""
    result = subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", 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 · scripts/narration_script_template.py (reported line 82)May include surrounding context.

python
segment_files.append((start, out_path))

    # Step 2: Get video duration
    video_duration = float(json.loads(subprocess.run(
        ["ffprobe", "-v", "quiet", "-print_format", "json", "-show_format", INPUT_VIDEO],
        capture_output=True, text=True
    ).stdout)["format"]["duration"])

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/narration_script_template.py (reported line 142)May include surrounding context.

python
]

    print("  Running ffmpeg...")
    result = subprocess.run(cmd, capture_output=True, text=True)
    if result.returncode != 0:
        print(f"  ffmpeg error:\n{result.stderr[-2000:]}")
        return False

Static analysis

No suspicious patterns detected.