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. ]]>
