Feb 24, 2026·6 min read·10 visits
If you use `yt-dlp` with the `--netrc-cmd` option to handle credentials, you are vulnerable to RCE. Attackers can embed shell commands in URLs (e.g., subdomains) which get passed unsanitized to your system shell. Update to version 2026.02.21 immediately.
A high-severity OS command injection vulnerability in yt-dlp allows attackers to execute arbitrary code via crafted URLs. By exploiting the `--netrc-cmd` feature and permissive hostname extraction logic, a malicious link can turn a video download into a full system compromise.
We all love yt-dlp. It is the Swiss Army knife of media archiving, capable of ripping video from the darkest corners of the internet with a single command. It’s a tool built by hackers, for hackers (and data hoarders). But like any complex tool with a million switches, some of those switches are connected to dynamite. Enter --netrc-cmd.
This specific flag is designed for the power users—the ones who don't just want to put their credentials in a static .netrc file. It allows you to specify a shell command that yt-dlp will execute to fetch credentials dynamically. You give it a template, like pass show /internet/{}, and yt-dlp replaces {} with the machine name (hostname) of the site you're downloading from.
Sounds convenient, right? It is, until you realize that "machine name" isn't always just youtube.com. For certain extractors, that machine name is derived directly from the URL you fed the tool. If you're letting the internet dictate the arguments to your shell commands without a bouncer at the door, you're asking for a bad time. And in CVE-2026-26331, the bouncer wasn't just on break; he didn't exist.
The core of this vulnerability lies in a classic misunderstanding of trust boundaries. The developers assumed that a hostname or a "machine identifier" extracted from a URL would be a benign string—something alphanumeric like vimeo or pornhub. For the vast majority of extractors, this is hardcoded. But for a select few—specifically GetCourseRuIE, TeachableIE, TeachableCourseIE, and PornHubIE—the extraction logic was dynamic.
These extractors pulled the "machine" identifier directly from the URL using regular expressions that were far too permissive. For example, they might look for anything that isn't a slash. This means a URL like https://;rm -rf /;@example.com could technically parse validly in terms of structure, but the "machine" part becomes ;rm -rf /;.
Here is the fatal error: yt-dlp takes this dirty input, stuffs it into your command template (replacing {}), and then hands the whole mess over to Python's subprocess.Popen with shell=True. Using shell=True is the programming equivalent of driving blindfolded. It tells the OS to interpret the string as a full shell command, metacharacters and all. The semicolon isn't just part of a string anymore; it's a command separator. The game is over.
Let's look at the "smoking gun." The vulnerability lived in how the netrc_machine variable was handled before being passed to the shell. Prior to the fix, the code essentially did this:
# Vulnerable Logic (Pseudocode)
cmd = self.params['netrc_cmd'].replace('{}', netrc_machine)
# The fatal flaw: shell=True
subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, ...)If netrc_machine contained ; calc.exe, the resulting command sent to the OS would be echo ; calc.exe. The shell sees an empty echo, finishes it, and then happily executes calc.exe.
The fix, implemented in commit 1fbbe29b99dc61375bf6d786f824d9fcf6ea9c1a, applies a strict whitelist approach. Instead of trying to play whack-a-mole with dangerous characters, the developers defined exactly what a valid machine name looks like.
# The Fix (yt_dlp/extractor/common.py)
ALLOWED = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_'
# ... inside _get_netrc_login_info ...
if netrc_machine.startswith(('-', '_')) or not all(c in ALLOWED for c in netrc_machine):
raise ExtractorError(f'Invalid netrc machine: {netrc_machine!r}', expected=True)Notice two things here: First, the ALLOWED string limits characters to alphanumerics, dots, dashes, and underscores. No semicolons, no ampersands, no backticks. Second, they explicitly ban strings starting with - or _. This prevents Argument Injection, where an attacker could theoretically pass a flag (like --output /etc/passwd) to the password manager command even if they couldn't execute arbitrary code.
Let's construct a realistic attack scenario. You are a security researcher (or a miscreant) targeting a user known to use yt-dlp with the --netrc-cmd flag. Perhaps they have a cron job that downloads videos from a list, or they use a wrapper script.
The Setup:
The victim runs yt-dlp with a command to fetch passwords:
yt-dlp --netrc-cmd "echo Password for {}" [URL]
The Payload:
You craft a malicious URL targeting the GetCourseRu extractor, which is vulnerable to this injection. You want to run id.
https://;id;#.getcourse.ru/video
The Execution Flow:
yt-dlp parses the URL. The regex captures ;id;# as the machine identifier.echo Password for ;id;#.echo Password for (prints text); (separator)id (EXECUTES THE PAYLOAD);# (comments out the rest of the line)> [!WARNING]
> The Redirect Trap: You don't even need the user to paste the ugly URL directly. yt-dlp follows redirects. You can send a link to https://tinyurl.com/cute-cat-video. yt-dlp resolves it, gets redirected to your malicious getcourse.ru payload, and triggers the RCE automatically. This makes the attack vector significantly wider, effectively turning any processed link into a potential bomb.
This is a High Severity (8.8) vulnerability for a reason. While it requires the user to enable a specific flag (--netrc-cmd), the consequences are catastrophic. We aren't talking about a crash or a memory leak; we are talking about arbitrary code execution.
If this runs on a developer's laptop, the attacker has access to SSH keys, source code, and browser cookies. If this runs on a server (e.g., a Discord bot that downloads videos or a web service wrapping yt-dlp), the attacker now owns that server. They can pivot into the internal network, install persistence, or exfiltrate data.
The fact that yt-dlp is often used in automated pipelines makes this particularly dangerous. A single malicious entry in a download queue could compromise the entire pipeline. The barrier to entry is low—just a crafted string—and the impact is total compromise of the user context running the application.
The remediation is straightforward: Update yt-dlp immediately.
The patch was released in version 2026.02.21. If you are running anything older (specifically between 2023.06.21 and 2026.02.21), you are exposed.
If you cannot update for some reason (perhaps you enjoy living dangerously or are stuck in corporate bureaucracy hell), you must audit your usage:
--netrc-cmd: Do not use this flag. Use a static .netrc file or standard credential passing mechanisms.yt-dlp.conf files. If you see --netrc-cmd with {} in it, comment it out.But really, just run yt-dlp -U. It takes five seconds and saves you from being the punchline of a security conference talk.
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
yt-dlp yt-dlp | >= 2023.06.21, < 2026.02.21 | 2026.02.21 |
| Attribute | Detail |
|---|---|
| CWE | CWE-78 (OS Command Injection) |
| CVSS | 8.8 (High) |
| Attack Vector | Network (via malicious URL) |
| Constraint | Requires --netrc-cmd usage |
| Exploit Status | Proof-of-Concept Available |
| Patch | Strict Alphanumeric Whitelist |
Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection')