yt-dlp is a command-line program that downloads video and audio from YouTube and from thousands of other sites, from news broadcasters and lecture platforms to podcasts and social networks. Give it a link and it fetches the best available quality; add a flag and it pulls a whole playlist or channel, extracts the audio as MP3, embeds subtitles, chapters and thumbnails, or picks a specific resolution and format.
It began as a fork of youtube-dl and has long since become the version everyone uses. Many graphical downloaders, media servers and AI transcription tools on this list run yt-dlp underneath, because its extractors are fixed within days whenever a site changes. Its own builds are single files for Windows, macOS and Linux that update themselves with yt-dlp -U.
It is released into the public domain under the Unlicense, with about 199,300 stars and 1,760 contributors, at release 2026.08.19 when scanned. Full YouTube support now needs a JavaScript runtime such as Deno alongside it, and ffmpeg is needed to merge video and audio and to convert formats.
- Repository: github.com/yt-dlp/yt-dlp
- Licence: Unlicense (The Unlicense)
- Language: Python. Stars: 199.3K. Forks: 17.4K. Last push: Oct 10, 2026.
- Scan: safe, Oct 19, 2026, commit 8973412
Who it is for
Anyone who wants an offline copy of videos, lectures or podcasts they are allowed to keep, archivists, researchers and developers who need media files for transcription or editing.
Getting started
1. Install with pip (or download the single-file build from the releases page)
python3 -m pip install -U "yt-dlp[default]"2. Download a video in the best quality
yt-dlp "https://www.youtube.com/watch?v=VIDEO_ID"3. Extract the audio as MP3
yt-dlp -x --audio-format mp3 "https://www.youtube.com/watch?v=VIDEO_ID"4. Update a standalone build
yt-dlp -UInstall ffmpeg and a JavaScript runtime (Deno is recommended) as well; without them some formats cannot be merged and YouTube support is incomplete. Homebrew, winget and most Linux package managers also carry yt-dlp.
Safety scan
We cloned yt-dlp/yt-dlp at commit 8973412 on Oct 19, 2026 and ran the checks described on the GitHub Tools page: credential patterns, decode-and-execute code, install-time scripts, committed binaries, risky CI workflows, every host the code talks to, known vulnerabilities in pinned dependencies, and project hygiene. A person read every hit. This is what we found.
- The 6 secret hits are API keys that sites embed in their own web players (Google Drive, Shahid and a few streaming services), which yt-dlp's extractors send the same way the official player does. They are not the project's credentials.
- The 11 pattern hits are test data: long hex-escaped strings in the tests for its JavaScript challenge solver, and one very long line in the tests for its JavaScript interpreter. The 3 bare-IP URLs are media servers that two extractors talk to. No committed binaries and no install hooks across 1,239 files and about 265,000 lines of Python.
- 7 known advisories (5 high) in uv.lock, all in urllib3 and virtualenv, which the lockfile pins for development and testing. yt-dlp's own builds bundle their dependencies separately, and pip installs current releases.
- Legally, yt-dlp is a general tool, and the project refuses to support sites that exist mainly for piracy. Whether a particular download is allowed depends on the site's terms and your country's copyright law.
- 13 workflows; the one pull_request_target workflow only reacts to labels with comments and never checks out PR code. 8 of 11 third-party actions are pinned to commits, CodeQL is on, and its OpenSSF Scorecard is 8 out of 10. No security policy file in the repository.
What the scanner counted
| Check | Result |
|---|---|
| Secrets | 6 candidates found and read; see the notes above. |
| Suspicious code | 11 pattern hits found and read; every one is listed under the raw findings. |
| Install-time code | None: nothing runs at install beyond the package manager itself. |
| Committed binaries | None. |
| CI workflows | 13 workflows. 1 uses pull_request_target, none check out the pull request head. 3 of 11 third-party actions pinned to a tag rather than a commit. |
| Network hosts | 40 distinct hosts referenced from source; most often www.youtube.com, github.com, vimeo.com, twitter.com. 3 URLs to a bare IP address, listed under the raw findings. |
| Known vulnerabilities | 7 advisories across 61 pinned packages: 0 critical, 5 high, 2 moderate, 0 low. uv.lock: 61 packages, 7 advisories. |
| Project hygiene | Has CodeQL, licence file, contributing guide. Missing security policy, automated dependency updates. |
| OpenSSF Scorecard | 8.0 out of 10, as of Oct 12, 2026. |
The raw findings
Every hit the scanner wrote out, with a link to the exact line at the scanned commit. Secrets candidates are redacted.
Secret candidates (6, redacted)
| Where | Rule | Match |
|---|---|---|
| yt_dlp/extractor/cybrary.py:12 | google-api-key | AIzaSy…zSw (39 chars) |
| yt_dlp/extractor/googledrive.py:144 | google-api-key | AIzaSy…nb8 (39 chars) |
| yt_dlp/extractor/shahid.py:39 | aws-access-key | AKIAI6…MUQ (20 chars) |
| yt_dlp/extractor/stacommu.py:15 | google-api-key | AIzaSy…Ahg (39 chars) |
| yt_dlp/extractor/stacommu.py:177 | google-api-key | AIzaSy…rfc (39 chars) |
| yt_dlp/extractor/wrestleuniverse.py:32 | google-api-key | AIzaSy…AdA (39 chars) |
Pattern hits (11)
| Where | Rule | Match |
|---|---|---|
| test/test_jsc/test_ejs_integration.py:65 | long-hex-escape-run (test/example) | '\x00\x01\x02%\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x30\x31\x32\x33\x34\x35\x36\x37\x38\x… |
| test/test_jsc/test_ejs_integration.py:66 | long-hex-escape-run (test/example) | '\x00\x01\x02%\x03\x04\x05\x06\x07\x08\x09\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x30\x31\x32\x33\x34\x35\x36\x37\x38\x… |
| test/test_jsc/test_ejs_integration.py:89 | long-hex-escape-run (test/example) | '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x2… |
| test/test_jsc/test_ejs_integration.py:90 | long-hex-escape-run (test/example) | '\x6a\x69\x68\x67\x66\x65\x64\x63\x62\x61\x60\x5f\x5e\x5d\x5c\x5b\x5a\x59\x58\x57\x56\x55\x54\x53\x52\x51\x50\x4f\x4e\x4d\x4c\x4b\x4a\x49\x48\x47\x46\x45\x44\x4… |
| test/test_jsc/test_ejs_integration.py:97 | long-hex-escape-run (test/example) | '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x2… |
| test/test_jsc/test_ejs_integration.py:98 | long-hex-escape-run (test/example) | '\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2… |
| test/test_jsc/test_ejs_integration.py:105 | long-hex-escape-run (test/example) | '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x2… |
| test/test_jsc/test_ejs_integration.py:106 | long-hex-escape-run (test/example) | '\x46\x66\x65\x64\x63\x62\x61\x60\x5f\x5e\x67\x6a\x5b\x5a\x59\x58\x57\x56\x55\x54\x53\x52\x51\x50\x4f\x4e\x4d\x4c\x4b\x4a\x49\x48\x47\x2c\x45\x44\x43\x42\x41\x4… |
| test/test_jsc/test_ejs_integration.py:113 | long-hex-escape-run (test/example) | '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x2… |
| test/test_jsc/test_ejs_integration.py:114 | long-hex-escape-run (test/example) | '\x19\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x00\x1a\x1b\x1c\x1d\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x2… |
| test/test_jsinterp.py:402 | very-long-line | 4455 chars |
URLs to bare IP addresses (3)
| Where | Rule | Match |
|---|---|---|
| yt_dlp/extractor/presstv.py:34 | ip-literal-url | base_url = 'http://192.99.219.222:82/presstv' |
| yt_dlp/extractor/saitosan.py:16 | ip-literal-url | 'thumbnail': 'http://111.171.196.85:8088/921f916f-7f55-4c97-b92e-5d9d0fef8f5f/thumb', |
| yt_dlp/extractor/saitosan.py:32 | ip-literal-url | 'thumbnail': 'http://111.171.196.85:8088/1a3933e1-a01a-483b-8931-af15f37f8082/thumb', |
Worst known vulnerabilities (7 of 7)
| Advisory | Severity | Package | Summary |
|---|---|---|---|
| GHSA-8988-9cw3-xx77 | high | urllib3@2.7.0 | urllib3: HTTPS proxy TLS configuration may be ignored or overridden |
| GHSA-vxq7-64xx-v4gw | high | urllib3@2.7.0 | urllib3: HTTPResponse.stream()/read_chunked() buffers an unbounded chunk-size line into memory |
| GHSA-94p9-xgh2-xp45 | high | virtualenv@21.7.4 | virtualenv: Downloaded seed wheels (pip/setuptools) are not integrity-checked before use |
| GHSA-p58f-9548-mpm2 | high | virtualenv@21.7.4 | virtualenv bash and fish activation scripts execute commands embedded in paths |
| GHSA-x78j-v8h9-3j2q | high | virtualenv@21.7.4 | virtualenv: Command injection via --prompt in activate.bat (batch activator) |
| GHSA-gh4c-6fx4-qh6g | moderate | urllib3@2.7.0 | urllib3: Chunked Deflate streaming can enter an infinite loop |
| GHSA-9h9j-4vrj-gf7g | moderate | virtualenv@21.7.4 | virtualenv writes prompt values into pyvenv.cfg without sanitizing line boundaries, allowing configuration injection |
Workflows worth a look
- .github/workflows/label-handler.yml: pull_request_target
By the numbers
| Stars | 199.3K |
|---|---|
| Forks | 17.4K |
| Contributors | 1,760 |
| Commits | 24K |
| Open issues | 2,045 |
| Open pull requests | 675 |
| Releases | 136 |
| Latest release | 2026.08.19 |
| Licence | Unlicense |
| Main language | Python |
| Project age | 5 years |
| Last push | Oct 10, 2026 |
| Tracked files | 1,239 |
| Lines of code | 265.1K |
| Checkout size | 12 MB |
Lines by language: Python 249.1K, Markdown 12.2K, YAML 2,693, JSON 549, TOML 485, Shell 92.
Questions
Is yt-dlp free?
Yes. yt-dlp is free and in the public domain under the Unlicense, with no account, ads or paid version. Be wary of paid apps that are only a wrapper around it.
Is it legal to use yt-dlp?
The tool itself is legal to use, and it is widely used for archiving, research and accessibility. Downloading a specific video may break the site's terms of service or copyright law, so stick to content you own, content under an open licence, or content the site lets you save.
Why does yt-dlp say a JavaScript runtime is needed for YouTube?
YouTube now requires solving JavaScript challenges to get some formats. yt-dlp does this with its yt-dlp-ejs component, which needs a runtime such as Deno, Node.js, Bun or QuickJS installed. Without one, it may only find a few formats or fail on some videos.
This post is part of GitHub Tools, where every repository is cloned and scanned before it is written up. The scan is a snapshot of one commit on one day; the repository has moved on since, so check it before you install.
