# Risk map — yt-dlp/yt-dlp

> Canonical page: https://codevetter.com/unpack/yt-dlp/risk-map

# Risk map — yt-dlp/yt-dlp

Scanned commit c7fb478d21e9e59524befbe23f7801bb267fb880 (2026-09-16). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Highest-sensitivity areas: the self-updater downloads and replaces the running executable from GitHub releases (sha256-verified but not signature-verified); `ExecPP` and external downloaders shell out to user-supplied commands; `cookies.py` decrypts browser credential stores; the plugin system executes arbitrary code from config dirs by default; and `jsinterp.py`/ejs remote components execute third-party JavaScript. Fragility concentrates in the ~1752 regex-driven extractors and the first-match-wins registry ordering. Note the repo carries an explicit NO AI / NO LLM contribution policy.
- POLICY: `.NO_AI/README.md` states the project strictly forbids LLM/AI contributions of any kind (code, issues, comments, translations) and instructs LLM agents to refuse interaction; CONTRIBUTING.md repeats it. Any agent must surface this before producing patches intended for upstream. — [.NO_AI/README.md](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.NO_AI/README.md) [CONTRIBUTING.md#L33-55](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/CONTRIBUTING.md#L33-L55)
- `ExecPP.run` executes commands via `Popen.run(cmd, shell=True)`; template expansion is safety-validated at `set_downloader` unless `allow-unsafe-exec-expansion` is in compat_opts — a deliberate but real command-injection surface when combined with attacker-controlled metadata. — [yt_dlp/postprocessor/exec.py#L12-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/exec.py#L12-L40) [yt_dlp/options.py#L1768-1783](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L1768-L1783)
- Self-update downloads a replacement binary over the network and swaps it in-place; integrity relies on a sha256 checksum from the same release (SHA2-256SUMS) rather than a signature, and when the checksum is missing restart is only blocked, not the install itself. — [yt_dlp/update.py#L471-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L471-L500) [yt_dlp/update.py#L537-568](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L537-L568)
- Windows update cleanup shells out: `atexit.register(Popen, 'ping 127.0.0.1 -n 5 -w 1000 & del /F ...', shell=True)` to delete the old binary after exit. — [yt_dlp/update.py#L569-572](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/update.py#L569-L572)
- Plugins execute arbitrary Python from user/system config dirs, `yt-dlp-plugins` dirs, and PYTHONPATH — enabled by default (`plugin_dirs=['default']`) unless `YTDLP_NO_PLUGINS` is set; the API explicitly disclaims backwards compatibility. — [yt_dlp/globals.py#L24](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L24) [yt_dlp/plugins.py#L38-40](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L38-L40) [yt_dlp/plugins.py#L194-234](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L194-L234)
- `cookies.py` reads and decrypts browser cookie databases (sqlite copies, AES-GCM/CBC via `aes.py`, OS keyrings via secretstorage/dbus, subprocess calls) — high-severity credential-handling code that reads outside the repo. — [yt_dlp/cookies.py#L1-50](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/cookies.py#L1-L50)
- `file://` URLs are refused by default for security and only enabled via `--enable-file-urls`; the special-case is handled in `urlopen` by inspecting `NoSupportingHandlers` reasons. — [yt_dlp/YoutubeDL.py#L4303-4311](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/YoutubeDL.py#L4303-L4311)
- Extractor fragility: ~1752 `_VALID_URL` regexes are evaluated in registry order (first match wins; YouTube hoisted first, `GenericIE` with `_VALID_URL='.*'` last), so adding or reordering an extractor can silently change which IE handles a URL. — [yt_dlp/extractor/extractors.py#L17-31](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/extractors.py#L17-L31) [yt_dlp/extractor/generic.py#L53-56](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/generic.py#L53-L56) [yt_dlp/extractor/common.py#L615-631](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/extractor/common.py#L615-L631)
- Remote JS execution: `--remote-components ejs:github`/`ejs:npm` downloads solver code fetched at runtime, and `jsinterp.py` interprets remote JavaScript for signature deciphering — remote-code supply-chain surface gated behind explicit options. — [yt_dlp/__init__.py#L1105-1106](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/__init__.py#L1105-L1106) [yt_dlp/options.py#L481-500](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/options.py#L481-L500) [yt_dlp/jsinterp.py](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/jsinterp.py)
- ffmpeg invocation builds argv programmatically and forces `file:`-prefixed filename arguments to defend against ffmpeg protocol tricks — the comment documents the attack class (filenames containing ':' or starting with '-'). — [yt_dlp/postprocessor/ffmpeg.py#L355-377](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/postprocessor/ffmpeg.py#L355-L377)
- Mutable global `Indirect` registries mean plugin classes are merged ahead of built-ins (`merge_dicts(regular_classes, destination)`), so a plugin can shadow a core extractor/postprocessor and behavior depends on load order. (inferred) — [yt_dlp/plugins.py#L229-234](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/plugins.py#L229-L234) [yt_dlp/globals.py#L18-28](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/yt_dlp/globals.py#L18-L28)
- Core CI excludes `yt_dlp/extractor/**` changes (except youtube/common/extractors), and download tests hit live third-party sites — so most extractor regressions are only caught by the flaky download tier, not the core gate. (inferred) — [.github/workflows/core.yml#L5-28](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/.github/workflows/core.yml#L5-L28) [test/test_download.py#L95-100](https://github.com/yt-dlp/yt-dlp/blob/c7fb478d21e9e59524befbe23f7801bb267fb880/test/test_download.py#L95-L100)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
