Back to skill

Security audit

youtube-transcript-native-node

Security checks for vulnerabilities and agentic risk

Overview

This skill is a disclosed YouTube caption extractor that uses yt-dlp, with a limited privacy caveat around forwarding full video URLs.

Install only if you are comfortable using a local yt-dlp binary to contact YouTube for the supplied video. Avoid private/client-sensitive videos and strip tracking, token, or other sensitive query parameters from YouTube URLs before using JSON or downstream logging.

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
scripts/fetch.mjs:517
Finding
Unnecessary Query Parameters in Valid YouTube URLs Are Forwarded to a Networked Process<![CDATA[ ## Vulnerability Details **File Location**: `scripts/fetch.mjs`, lines 99–117, 517–531, and 633–642 **Vulnerability Type**: Sensitive data exposure through insufficient URL minimization **Risk Level**: Medium ### Vulnerable Code The URL validator confirms the host and video shape but permits arbitrary additional query parameters and fragments: ```js function isSingleVideoYouTubeUrl(parsed) { const host = parsed.hostname.toLowerCase(); const path = parsed.pathname || "/"; if (host === "youtu.be") return /^\/[A-Za-z0-9_-]{6,}$/.test(path); if (!["youtube.com", "www.youtube.com", "m.youtube.com"].includes(host)) return false; if (path === "/watch") return Boolean(parsed.searchParams.get("v")); return /^\/(?:shorts|live|embed)\/[A-Za-z0-9_-]{6,}$/.test(path); } export function isAllowedYouTubeUrl(rawUrl) { let parsed; try { parsed = new URL(rawUrl); } catch { return false; } if (parsed.protocol.toLowerCase() !== "https:") return false; if (parsed.username || parsed.password) return false; return isSingleVideoYouTubeUrl(parsed); } ``` The complete accepted URL is then passed unchanged to the network-capable `yt-dlp` process: ```js const ytArgs = [ "--skip-download", "--write-subs", "--write-auto-subs", "--sub-lang", args.lang, "--sub-format", "vtt", "--no-playlist", "--no-warnings", "--ignore-config", "--no-cache-dir", "--no-plugin-dirs", "--print-json", "-o", outTemplate, "--", args.url, ]; ``` In JSON mode, the same unmodified URL is included in output: ```js const payload = { url: args.url, title, lang: args.lang, auto, timestamps: args.timestamps, transcript: enforceTranscriptSize(transcript), }; process.stdout.write(JSON.stringify(payload, null, 2) + "\n"); ``` ### Technical Analysis For a `/watch` URL, validation only requires a nonempty `v` parameter. It does not reject or remove unrelated query parameters or the URL fragment. For example, the following URL is valid ...[truncated 2301 chars]
Remediation
<![CDATA[ ## Remediation Suggestions Parse and canonicalize every accepted URL before invoking `yt-dlp` or generating output: 1. Extract and validate only the video identifier required for the supported URL shape. 2. Reconstruct a canonical HTTPS YouTube URL rather than forwarding the original input. 3. For `/watch` URLs, retain only the validated `v` parameter unless another parameter is explicitly documented as necessary. 4. Remove URL fragments in all cases. 5. For `youtu.be`, `/shorts/`, `/live/`, and `/embed/` URLs, reject or remove unrelated query parameters. 6. Use the canonical URL in both the `yt-dlp` argument array and JSON output. 7. Consider rejecting inputs containing unexpected parameters when silent removal could surprise users. Example hardening approach: ```js function canonicalizeYouTubeUrl(rawUrl) { const parsed = new URL(rawUrl); if (!isAllowedYouTubeUrl(rawUrl)) return null; const host = parsed.hostname.toLowerCase(); const path = parsed.pathname; if (host === "youtu.be") { return `https://youtu.be${path}`; } if (path === "/watch") { const videoId = parsed.searchParams.get("v"); return `https://www.youtube.com/watch?v=${encodeURIComponent(videoId)}`; } return `https://www.youtube.com${path}`; } ``` Store the canonical result in the parsed arguments and use it at both sinks: ```js const canonicalUrl = canonicalizeYouTubeUrl(out.url); if (!canonicalUrl) { die("..."); } out.url = canonicalUrl; ``` Add offline regression tests that capture the fake child process arguments and verify that values such as `token=secret-canary` and fragments never reach either child argv or JSON output for otherwise valid YouTube URLs. ]]>
Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • 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
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
Findings (4)

Ae1

High
Category
analysis-evasion
Content
Script: `scripts/fetch.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Ae1

High
Category
analysis-evasion
Content
Script: `scripts/fetch.mjs`
Confidence
100% confidence
Finding
Referenced artifact was not completely inspected

Credential Access

High
Category
Privilege Escalation
Content
function runFetch(args, env = process.env) {
  const fixtureScript = env.YOUTUBE_TRANSCRIPT_SELFTEST_FIXTURE_ARG || "";
  const childEnv = { ...env };
  delete childEnv.YOUTUBE_TRANSCRIPT_SELFTEST_FIXTURE_ARG;
  const fetchArgs = fixtureScript ? ["--self-test-fixture", fixtureScript, ...args] : args;
  return spawnSync(process.execPath, ["scripts/fetch.mjs", ...fetchArgs], {
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Lp3

Medium
Category
MCP Least Privilege
Confidence
88% confidence
Finding
The skill explicitly describes capabilities to access the network via `yt-dlp` and to expose selected environment state to a child process, but it does not declare an explicit tool/permission scope such as `permissions` or `allowed-tools`. That creates a governance gap: callers or reviewers may not get enforceable policy boundaries for network and environment access, increasing the chance of unintended invocation in sensitive contexts.

Static analysis

Detected: suspicious.dangerous_exec

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/fetch.mjs:292

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
scripts/self-test.mjs:25