Back to skill

Security audit

youtube-transcript

Security checks for vulnerabilities and agentic risk

Overview

The skill has a clear YouTube transcript purpose, but it lets user-supplied inputs drive broad network fetching and arbitrary file writes without enough containment.

Review this before installing. Use it only with trusted YouTube URLs, avoid letting untrusted content choose the output path, and prefer stdout or a dedicated transcript directory. The package does not show malicious intent, but it should add URL allowlisting and safe output-file handling before being treated as low-risk.

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
yt_transcript.py:13
Finding
Unrestricted URL Enables Unintended Network Requests<![CDATA[ ## Vulnerability Details **File Location**: `yt_transcript.py:13-27` **Vulnerability Type**: Server-Side Request Forgery / Unrestricted Network Target **Risk Level**: Medium ### Vulnerable Code ```python def download_subs(url: str, lang: str = "en") -> str: """Download auto-generated subtitles and return the VTT content.""" with tempfile.TemporaryDirectory() as tmp: out = Path(tmp) / "sub" subprocess.run( [ "yt-dlp", "--write-auto-sub", "--write-sub", "--skip-download", "--sub-lang", lang, "-o", str(out), url, ], check=True, capture_output=True, text=True, ) ``` ### Technical Analysis The command-line URL is passed directly to the network-capable `yt-dlp` program without validating its scheme, hostname, port, or resolved destination. Although the Skill is documented as accepting YouTube URLs, the implementation does not enforce that restriction. `subprocess.run` uses an argument list and does not enable `shell=True`, so this is not a shell-command injection vulnerability. The risk instead arises because `yt-dlp` supports multiple websites and generic URL extraction. An attacker who can influence the URL given to the Agent may cause the host running the Skill to contact a destination outside the declared YouTube service. This could include attacker-controlled servers or services reachable only from the Agent's network environment. Redirect behavior may also undermine a hostname-only check unless redirect destinations and resolved addresses are constrained. ### Attack Path 1. An attacker supplies a crafted non-YouTube URL while requesting transcript extraction. 2. The Agent follows the documented workflow and invokes `yt_transcript.py` with that URL. 3. `download_subs()` forwards the URL to `yt-dlp` without validation. 4. `yt-dlp` attempts to a ...[truncated 941 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Parse the input with `urllib.parse.urlparse` before invoking `yt-dlp`. 2. Require the `https` scheme and reject URLs containing embedded credentials. 3. Allowlist the intended YouTube hostnames, such as: - `youtube.com` - `www.youtube.com` - `m.youtube.com` - `youtu.be` 4. Normalize hostnames before comparison and use exact-name or controlled-subdomain matching rather than substring checks. 5. Resolve the destination and reject loopback, link-local, private, multicast, reserved, and unspecified IP address ranges. 6. Account for DNS rebinding and redirects by validating the destination after resolution and constraining redirect targets where supported. 7. Run the downloader in a sandbox with egress restricted to required YouTube endpoints. 8. Apply execution timeouts and resource limits to reduce denial-of-service exposure. Example initial validation: ```python from urllib.parse import urlparse ALLOWED_HOSTS = { "youtube.com", "www.youtube.com", "m.youtube.com", "youtu.be", } def validate_youtube_url(value: str) -> str: parsed = urlparse(value) host = (parsed.hostname or "").lower().rstrip(".") if parsed.scheme != "https" or host not in ALLOWED_HOSTS: raise ValueError("Only HTTPS YouTube URLs are permitted") if parsed.username is not None or parsed.password is not None: raise ValueError("URL credentials are not permitted") return value ``` ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
yt_transcript.py:101
Finding
Caller-Controlled Output Path Can Overwrite Arbitrary Writable Files<![CDATA[ ## Vulnerability Details **File Location**: `yt_transcript.py:83, 101-102` **Vulnerability Type**: Arbitrary Writable-File Overwrite **Risk Level**: Medium ### Vulnerable Code ```python parser.add_argument("-o", "--output", help="Output file (default: stdout)") ``` ```python if args.output: Path(args.output).write_text(transcript, encoding="utf-8") print(f"Saved to {args.output}", file=sys.stderr) else: print(transcript) ``` ### Technical Analysis The `--output` argument accepts an unrestricted filesystem path. The path is passed to `Path.write_text()`, which opens an existing target for writing and truncates its previous contents. The implementation performs no workspace containment check, file-existence check, symlink check, file-type validation, or overwrite confirmation. Consequently, anyone able to influence the arguments used by the Agent can select any file writable by the operating-system account running the Skill. Relative traversal paths such as `../../target` and absolute paths are accepted. A path that resolves through a symbolic link may also direct the write to another writable file. The written data is the parsed transcript. If an attacker controls the selected video's subtitle content, they may also influence the content placed into the target file, subject to the VTT parsing transformations. ### Attack Path 1. An attacker supplies a video URL with available subtitles and requests that the transcript be saved using a crafted `-o` path. 2. The crafted path identifies an existing file writable by the Agent process, either directly, through directory traversal, or through a symbolic link. 3. The script downloads and parses the subtitles. 4. `Path(args.output).write_text(...)` opens the selected target and truncates its existing contents. 5. The parsed transcript replaces the original file contents. 6. If the target is a configuration, project, or startup-related file, the overwrite may disrupt later operations or alt ...[truncated 804 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Restrict output to a dedicated, approved directory under the Agent workspace. 2. Resolve both the approved directory and requested destination with `Path.resolve()` and verify that the destination remains inside the approved directory. 3. Reject absolute paths when they are unnecessary. 4. Reject path traversal and destinations that resolve through symbolic links. 5. Refuse to overwrite an existing file by default. Require an explicit trusted `--force` option if replacement is necessary. 6. Create files atomically and exclusively, for example with mode `"x"`, to reduce overwrite and race-condition risks. 7. Validate that the destination is a regular file and that every parent directory is trusted. 8. Run the Skill using an account with minimal filesystem permissions. Example containment and exclusive-creation logic: ```python OUTPUT_ROOT = Path.cwd() / "transcripts" def safe_output_path(name: str) -> Path: OUTPUT_ROOT.mkdir(parents=True, exist_ok=True) root = OUTPUT_ROOT.resolve() target = (root / name).resolve() if target == root or root not in target.parents: raise ValueError("Output path must remain inside the transcript directory") if target.exists() or target.is_symlink(): raise FileExistsError("Refusing to overwrite an existing destination") return target target = safe_output_path(args.output) with target.open("x", encoding="utf-8") as output_file: output_file.write(transcript) ``` ]]>
Vulnerability Patterns
  • 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
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (4)

Lp3

Medium
Category
MCP Least Privilege
Confidence
92% confidence
Finding
The skill invokes a Python script, depends on `yt-dlp`, and explicitly describes reading and writing transcript files, which implies shell execution plus filesystem access. Because the manifest declares no `permissions` or `allowed-tools`, the runtime scope is under-specified and can allow broader-than-intended capabilities, reducing reviewability and increasing the chance of misuse or unsafe invocation paths.

Vague Triggers

Medium
Confidence
90% confidence
Finding
The activation text is fairly broad: it triggers when a user provides a YouTube URL or wants a transcript from a podcast, interview, or talk, which could match many general summarization or content-processing requests. Over-broad routing can cause the skill to be selected in situations where shelling out to external tools and writing files is unnecessary, expanding exposure to network, shell, and file operations beyond what the user likely intended.

subprocess module call

Medium
Category
Dangerous Code Execution
Content
"""Download auto-generated subtitles and return the VTT content."""
    with tempfile.TemporaryDirectory() as tmp:
        out = Path(tmp) / "sub"
        subprocess.run(
            [
                "yt-dlp",
                "--write-auto-sub",
Confidence
70% confidence
Finding
subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Natural-Language Policy Violations

Low
Confidence
95% confidence
Finding
The function default `lang: str = "en"` establishes English as the implicit language, and the CLI help at L086 repeats that English is the default. This is a natural-language locale policy concern because the skill applies a specific language unless the user overrides it, rather than prompting or detecting preference.

Static analysis

No suspicious patterns detected.