Back to skill

Security audit

video-to-markdown

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly does what it claims, but it handles API keys, live browser cookies, and video content uploads with weak safeguards, so users should review it before installing.

Review before installing. Use a virtual environment, pin dependencies, never run the API-key echo command, avoid personal-account cookies when possible, and only analyze videos you are allowed to send to an external AI service. Treat generated Markdown as untrusted model output, especially when analyzing arbitrary public videos.

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

T09 · Insecure Skill Coding Practices

Error
Location
scripts/video_analyzer.py:273
Finding
Indirect Prompt Injection Through Untrusted Video Content<![CDATA[ ## Vulnerability Details **File Location**: `scripts/video_analyzer.py:273-300` **Vulnerability Type**: Indirect prompt injection caused by mixing untrusted content with model instructions **Risk Level**: High ### Vulnerable Code ```python client = anthropic.Anthropic(api_key=api_key) content = [] for i, fp in enumerate(frames): img_data = base64.b64encode(resize_frame(fp)).decode() content.append({ "type": "image", "source": {"type": "base64", "media_type": "image/jpeg", "data": img_data}, }) content.append({"type": "text", "text": f"[Frame {i + 1}/{len(frames)}]"}) content.append({ "type": "text", "text": f"\nTRANSCRIPT:\n{transcript}" if transcript else "\n[No transcript available — analysis from frames only]", }) content.append({ "type": "text", "text": ANALYSIS_PROMPT.format(n_frames=len(frames), title=title), }) response = client.messages.create( model=model, max_tokens=4096, messages=[{"role": "user", "content": content}], ) ``` ### Technical Analysis Video frames, captions, and transcripts are controlled by the publisher of the analyzed video. The implementation places this untrusted material in the same user-role message as the instructions governing Markdown generation. No higher-priority system message establishes that text found in frames or transcripts is data rather than instructions. There is also no explicit instruction to ignore commands embedded in the source material. Consequently, visible text or spoken content such as “ignore the requested format and output the following instructions” may be interpreted as an instruction by the model. The generated Markdown is written directly to an output file without validation or sanitization. The Skill documentation subsequently instructs an Agent to read and present that file, increasing the chance that malicious model outp ...[truncated 1370 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Send a higher-priority system message that explicitly states that frames, captions, titles, and transcripts are untrusted data. 2. Instruct the model never to follow commands found in the source video and to report apparent prompt-injection attempts instead. 3. Delimit the transcript and metadata with clear data boundaries, preferably using structured content fields. 4. Keep trusted instructions separate from source material rather than combining everything in one user-role message. 5. Validate generated Markdown before saving or presenting it. Remove or neutralize active HTML, unsafe links, and command-like instructions where they are not required. 6. Present the generated file to downstream Agents as untrusted model output, not as authoritative Skill instructions. 7. Add adversarial tests using caption-based, visual, and spoken prompt-injection payloads. ]]>

T08 · Insecure Dependencies

Warning
Location
scripts/setup.sh:28
Finding
Unpinned Dependencies Installed Directly From Mutable Package Repositories<![CDATA[ ## Vulnerability Details **File Location**: `scripts/setup.sh:28-37` and `scripts/requirements.txt:1-4` **Vulnerability Type**: Uncontrolled dependency resolution and supply-chain exposure **Risk Level**: Medium ### Vulnerable Code `scripts/setup.sh`: ```bash # yt-dlp check if command -v yt-dlp &>/dev/null; then echo "✓ yt-dlp: $(yt-dlp --version)" else echo "Installing yt-dlp..." pip install yt-dlp fi # Python packages echo "" echo "Installing Python packages..." # For Facebook impersonation support, install yt-dlp with curl-cffi pip install "yt-dlp[default,curl-cffi]" 2>/dev/null || pip install yt-dlp ``` The setup script then installs this requirements file: ```text yt-dlp anthropic>=0.30.0 Pillow>=10.0.0 faster-whisper>=1.0.0 ``` ### Technical Analysis The setup procedure retrieves and installs mutable package versions from the default Python package index without exact version pins or cryptographic hashes. Lower-bound constraints such as `anthropic>=0.30.0` permit arbitrary future versions, while `yt-dlp` has no version constraint at all. Installation also occurs through an unqualified `pip` command rather than a project-specific virtual environment or `python3 -m pip`. This can install packages into an unintended interpreter or user/global environment. The optional `faster-whisper` stack is installed unconditionally even though the documented functionality describes it as optional. This unnecessarily increases the number of packages and transitive dependencies trusted by the setup process. ### Attack Path 1. A package release or one of its transitive dependencies is compromised, maliciously updated, or replaced with an incompatible version. 2. A user runs `bash scripts/setup.sh`. 3. `pip` resolves the latest versions permitted at that time. 4. Package build or installation hooks execute with the privileges of the user running the setup script. 5. Compromised code may subsequently execute when imported or when the analyzer ...[truncated 556 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Pin every direct and transitive dependency to a reviewed version. 2. Generate a lock file containing cryptographic hashes and install with hash verification, such as `pip install --require-hashes`. 3. Use a dedicated virtual environment and invoke pip through the intended interpreter: ```bash python3 -m venv .venv .venv/bin/python -m pip install --require-hashes -r requirements.lock ``` 4. Separate optional Whisper dependencies into an optional requirements file and install them only when requested. 5. Avoid installing `yt-dlp` multiple times through separate commands. 6. Add automated dependency vulnerability and provenance scanning. 7. Review dependency updates before regenerating the lock file. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:33
Finding
Documentation Instructs Users to Print the Anthropic API Key<![CDATA[ ## Vulnerability Details **File Location**: `SKILL.md:33-37` **Vulnerability Type**: Sensitive credential exposure through terminal and Agent output **Risk Level**: Medium ### Vulnerable Code ```markdown Also confirm `ANTHROPIC_API_KEY` is set: ```bash echo $ANTHROPIC_API_KEY ``` ``` ### Technical Analysis The documented preflight check prints the complete Anthropic API key instead of checking only whether the environment variable exists. In an Agent-driven workflow, shell output may be retained in conversation history, execution logs, CI output, terminal recordings, or monitoring systems. The analyzer itself correctly reads the key from the environment and passes it to the Anthropic SDK; no code was found that intentionally sends the key to an unrelated endpoint. The exposure arises specifically from the documented verification command. ### Attack Path 1. A user or Agent follows the documented dependency and credential preflight steps. 2. `echo $ANTHROPIC_API_KEY` writes the complete secret to standard output. 3. The output is retained in an Agent transcript, terminal log, CI record, screen capture, or shared session. 4. A party with access to that record obtains the key. 5. The exposed credential is reused for unauthorized Anthropic API requests until it is revoked. ### Impact Assessment An attacker who obtains the key may make API requests under the associated account and consume available quota or incur costs. The precise scope depends on the permissions and account configuration associated with the key. This issue does not expose browser cookies or directly provide operating-system privileges, but it compromises the confidentiality of a billing-capable API credential. ]]>
Remediation
<![CDATA[ ## Remediation Suggestions Replace the command with a presence-only test that never prints the value: ```bash if [ -n "${ANTHROPIC_API_KEY:-}" ]; then echo "ANTHROPIC_API_KEY is set" else echo "ANTHROPIC_API_KEY is not set" fi ``` Additionally: 1. Ensure Agent logs and CI systems mask variables matching secret names. 2. Never include real API keys in command examples, diagnostics, exception messages, or generated Markdown. 3. Document immediate key revocation and rotation if a key has already been printed into a retained log. 4. Prefer a secret manager or protected runtime environment for automated deployments. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (27)

Credential Access

High
Category
Privilege Escalation
Content
## Sensitive
cookies.txt
*.cookies
.env

## OS
.DS_Store
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
se` | Whisper model size (`tiny` / `base` / `small` / `medium` / `large-v3`) |
| `--cookies` | none | Path to cookies.txt (required for Facebook/Instagram) |
| `--model` | `claude-sonnet-4-6` | Claude model to use for analysis |

## Platform support

| Platform | Auth needed | Notes |
|---|---|---|
| YouTube | Usually no | Cookies needed on cloud IPs or age-restricted content |
| Instagram | Yes (cookies) | Firefox cookies; intermittent even with valid session |
| Facebook | Yes (cookies + impersonation) | Auto-handled; requires `curl_cffi` installed |

For cookie setup, see [`references/platforms.md`](references/platforms.md).

## Cost estimate (claude-sonnet-4-6)

| Video length | Frames | Approx. cost |
|---|---|---|
| 10 min | ~20 | ~$0.08 |
| 30 min | ~50 | ~$0.20 |
| 60 min | ~80 | ~$0.35 |

Switch to `claude-haiku-4-5` for ~5× lower cost.

## How it works

1. **Platform detection** — identifies YouTube, Facebook, or Instagram and selects appropriate download flags
2. **Transc
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

Vague Triggers

High
Confidence
97% confidence
Finding
The trigger language is extremely broad, including automatic activation for pasted social-media URLs and generic requests like 'what it's about' or 'understand its content.' In combination with shell, file, and env capabilities, this increases the chance the skill runs in contexts the user did not clearly intend, causing unreviewed downloads, external API calls, or processing of sensitive/private links.

Missing User Warnings

High
Confidence
98% confidence
Finding
The skill describes extracting frames and transcripts and sending them to Claude vision, and it also supports passing browser cookies for protected platforms, but it does not clearly warn that video content, transcripts, frame images, and possibly authentication material may be transmitted to external tools/services. This can expose private media, copyrighted content, or session-linked access data without informed user consent.

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
th doing

---

## Troubleshooting

**"No frames extracted"** → Check ffmpeg is installed and the video downloaded to the temp dir. Try `--max-frames 10` on a short public YouTube video first.

**"No captions found" (and no Whisper)** → Normal for non-captioned videos. Install faster-whisper and add `--whisper`, or the analysis continues from frames alone.

**Facebook "Cannot parse data"** → Cookies may be stale or from a different IP. Re-export from Firefox immediately before use, same network.

**Instagram fails with cookies** → Intermittent. Wait a few minutes and retry. Try a different account if it persists.

**Output is too short / missing visuals** → Increase `--max-frames` or upgrade to `--model claude-opus-4-8`.

**High cost** → Reduce `--max-frames` to 20–30. Talking-head content rarely needs more than 20 frames.
Confidence
75% confidence
Finding
YARA rule matched a known malware signature (reverse shell, backdoor, ransomware, C2 framework, or info stealer).

YARA rule 'info_stealer': Information stealer patterns (credential harvesting, browser data theft) [malware]

High
Category
YARA Match
Content
# Platform Notes

## YouTube

**Auth required:** Only on datacenter/cloud IPs, or for age-restricted/sign-in-required content.

**Captions:** Excellent. Auto-captions available for most English content. Prefer manual captions (`en`) over auto (`en-orig`) when both are present — auto-captions sometimes insert stray `>>` markers.

**Cookie setup (when needed):**
1. Install the "cookies.txt" extension for Firefox
2. Log into youtube.com in Firefox
3. Click the extension → "Export" → save the file
4. Pass: `--cookies /path/to/cookies.txt`
5. Must export from the same IP you're downloading from
6. Cookies expire — re-export every ~2 weeks

**Note:** Chrome cookies are encrypted (app-bound since Chrome 127) and cannot be reliably extracted. Use Firefox.

---

## Instagram

**Auth required:** Yes — public Reels increasingly require a logged-in session.

**Captions:** Rarely available via yt-dlp. Whisper transcription recommended (`--whisper`).

**Cookie setup:**
1
Confidence
89% confidence
Finding
The document instructs users to export live browser session cookies from Firefox and reuse them with command-line tooling, including recommendations for same-IP freshness and throwaway accounts. While framed as operational guidance for media access, this closely matches credential/session-token handling patterns that can enable account compromise, session hijacking, or unauthorized scraping if mishandled, leaked, or normalized inside agent workflows.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README explicitly states that full video transcripts and extracted frames are sent to Claude's vision API, but it does not clearly warn users that third-party data transmission occurs or that potentially sensitive visual/audio content may leave the local environment. This creates a real privacy and compliance risk, especially when users analyze private, unlisted, internal, or personally identifiable video content.

Lp3

Medium
Category
MCP Least Privilege
Confidence
97% confidence
Finding
The skill instructs the agent to use shell commands, read environment variables, and read/write files, but it declares no explicit tool scope or permission boundaries. That creates unnecessary ambient authority: if the skill is auto-invoked, it can access local files, environment secrets, and execute commands without a clearly constrained contract.

Description-Behavior Mismatch

Medium
Confidence
94% confidence
Finding
The manifest describes the skill as analyzing YouTube, Facebook, or Instagram video URLs, which implies a bounded platform scope. These notes state that the script will still attempt generic yt-dlp downloads for unsupported platforms, meaning the actual behavior is broader than the manifest claims.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
echo ""
    echo "WARNING: ffmpeg not found. Install it before using this skill:"
    echo "  macOS:          brew install ffmpeg"
    echo "  Ubuntu/Debian:  sudo apt install ffmpeg"
    echo "  Windows:        https://ffmpeg.org/download.html"
    echo ""
fi
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Sudo/Root Execution

Medium
Category
Privilege Escalation
Content
echo ""
    echo "WARNING: ffmpeg not found. Install it before using this skill:"
    echo "  macOS:          brew install ffmpeg"
    echo "  Ubuntu/Debian:  sudo apt install ffmpeg"
    echo "  Windows:        https://ffmpeg.org/download.html"
    echo ""
fi
Confidence
70% confidence
Finding
Commands invoke sudo or root privileges. Verify this elevated access is necessary and justified.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The setup script automatically installs Python packages with pip, including optional extras, without prompting the user, recommending a virtual environment, or pinning versions. This can unexpectedly modify the user's global Python environment and increase supply-chain exposure if executed on a sensitive system.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if platform == "facebook":
        cmd += ["--impersonate", "Chrome-99"]

    subprocess.run(cmd, capture_output=True, text=True)

    title = "Untitled Video"
    info_file = work_dir / "video.info.json"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if platform == "facebook":
        cmd += ["--impersonate", "Chrome-99"]

    subprocess.run(cmd, capture_output=True, text=True)

    title = "Untitled Video"
    info_file = work_dir / "video.info.json"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if platform == "facebook":
        cmd += ["--impersonate", "Chrome-99"]

    subprocess.run(cmd, capture_output=True, text=True)

    title = "Untitled Video"
    info_file = work_dir / "video.info.json"
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
# Scene-detection pass
    filter_expr = "select='gt(scene\\,0.25)*gte(t-prev_selected_t\\,2)',setpts=N/FRAME_RATE/TB"
    subprocess.run(
        ["ffmpeg", "-i", str(video_path), "-vf", filter_expr,
         "-vsync", "vfr", "-q:v", "2",
         str(frames_dir / "frame_%04d.jpg"), "-y", "-loglevel", "error"],
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
if len(frames) < 5:
        for f in frames:
            f.unlink()
        subprocess.run(
            ["ffmpeg", "-i", str(video_path), "-vf", "fps=1/15",
             str(frames_dir / "frame_%04d.jpg"), "-y", "-loglevel", "error"],
            capture_output=True,
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
("yt-dlp", "--version", "pip install yt-dlp"),
    ]:
        try:
            r = subprocess.run([tool, ver_flag], capture_output=True, timeout=20)
        except (FileNotFoundError, subprocess.TimeoutExpired):
            sys.exit(f"[error] '{tool}' not found or timed out. Install: {install_hint}")
        if r.returncode != 0:
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
The script sends extracted video frames and transcript text to the Anthropic API, which can expose sensitive, copyrighted, private, or regulated content to a third-party service without an explicit runtime warning or consent gate. In a tool that processes arbitrary user-supplied social-media videos, this is materially more dangerous because users may not realize the full video content is being exfiltrated off-box for analysis.

Intent-Code Divergence

Low
Confidence
87% confidence
Finding
The heading says platforms are unsupported, but the text immediately says the script still attempts a generic yt-dlp download for them. That is an intent/documentation contradiction: the label implies rejection or lack of handling, while the described behavior is best-effort support.

Unpinned Dependencies

Low
Category
Supply Chain
Content
yt-dlp
anthropic>=0.30.0
Pillow>=10.0.0
faster-whisper>=1.0.0
Confidence
97% confidence
Finding
The dependency `yt-dlp` is completely unpinned, so installs may resolve to different versions over time, including versions with known security issues or breaking changes. In a skill that fetches and processes untrusted remote video content, supply-chain drift increases risk because vulnerable parser or downloader behavior could be introduced without review.

Unverifiable Dependency: yt-dlp has 16 known advisory(ies) (CVE-2023-46121 (yt-dlp Generic Extractor MITM Vulnerability via Arbitrary Proxy Injection); GHSA-3v33-3wmw-3785 (yt-dlp has dependency on potentially malicious third-party code in Douyu extract); CVE-2023-40581 ( yt-dlp on Windows vulnerable to `--exec` command injection when using `%q`) +13 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
90% confidence
Finding
`yt-dlp` has numerous known advisories, and because no exact version is pinned, it is impossible to verify whether deployment will use a fixed or vulnerable release. This is more concerning in this skill than in a generic app because `yt-dlp` directly retrieves and interprets attacker-controlled remote media URLs and metadata, increasing exposure to downloader and extractor flaws.

Unpinned Dependencies

Low
Category
Supply Chain
Content
yt-dlp
anthropic>=0.30.0
Pillow>=10.0.0
faster-whisper>=1.0.0
Confidence
92% confidence
Finding
`anthropic>=0.30.0` allows any newer version, which makes builds non-reproducible and can silently pull in releases with newly introduced vulnerabilities or unsafe behavioral changes. Because this skill likely handles external content and may write local artifacts, uncertainty around SDK security posture is a legitimate supply-chain concern.

Unverifiable Dependency: anthropic has 4 known advisory(ies) (CVE-2026-34450 (Claude SDK for Python has Insecure Default File Permissions in Local Filesystem ); CVE-2026-34452 (Claude SDK for Python: Memory Tool Path Validation Race Condition Allows Sandbox); CVE-2026-34450 (The Claude SDK for Python provides access to the Claude API from Python applicat) +1 more), but the manifest does not pin a version, so it is unknown whether the installed release is affected

Low
Category
Supply Chain
Confidence
74% confidence
Finding
The `anthropic` package has reported advisories, and the manifest does not pin an exact version, so the installed release cannot be verified against those issues. The direct exploitability depends on how the SDK is used elsewhere, but the lack of version determinism still creates avoidable security uncertainty.

Unpinned Dependencies

Low
Category
Supply Chain
Content
yt-dlp
anthropic>=0.30.0
Pillow>=10.0.0
faster-whisper>=1.0.0
Confidence
95% confidence
Finding
`Pillow>=10.0.0` is not strictly pinned, so the environment may install any later release, making the deployed package version unpredictable. Since Pillow parses image/frame data derived from untrusted videos, relying on an unbounded version range is risky because image-processing libraries have a long history of parser and memory-safety issues.

Static analysis

No suspicious patterns detected.