VYPR
High severityNVD Advisory· Published Feb 24, 2026· Updated Feb 24, 2026

yt-dlp: Arbitrary Command Injection when using the `--netrc-cmd` option

CVE-2026-26331

Description

yt-dlp is a command-line audio/video downloader. Starting in version 2023.06.21 and prior to version 2026.02.21, when yt-dlp's --netrc-cmd command-line option (or netrc_cmd Python API parameter) is used, an attacker could achieve arbitrary command injection on the user's system with a maliciously crafted URL. yt-dlp maintainers assume the impact of this vulnerability to be high for anyone who uses --netrc-cmd in their command/configuration or netrc_cmd in their Python scripts. Even though the maliciously crafted URL itself will look very suspicious to many users, it would be trivial for a maliciously crafted webpage with an inconspicuous URL to covertly exploit this vulnerability via HTTP redirect. Users without --netrc-cmd in their arguments or netrc_cmd in their scripts are unaffected. No evidence has been found of this exploit being used in the wild. yt-dlp version 2026.02.21 fixes this issue by validating all netrc "machine" values and raising an error upon unexpected input. As a workaround, users who are unable to upgrade should avoid using the --netrc-cmd command-line option (or netrc_cmd Python API parameter), or they should at least not pass a placeholder ({}) in their --netrc-cmd argument.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

A command injection vulnerability in yt-dlp's `--netrc-cmd` option allows arbitrary code execution via a maliciously crafted URL.

Vulnerability

Overview

CVE-2026-26331 is a command injection vulnerability in yt-dlp, a command-line audio/video downloader. The flaw. The issue affects versions starting from 2023.06.21 up to (but not including) 2026.02.21. The root cause lies in the --netrc-cmd command-line option (or netrc_cmd Python API parameter), which allows users to specify a shell command to retrieve login credentials. When a placeholder {} is used in the command, it is replaced with the netrc "machine" value. In certain extractors (GetCourseRuIE, TeachableIE, TeachableCourseIE), this machine value is dynamically sourced from the site's hostname, and wildcard matches are allowed for subdomains. An attacker can craft a URL with a malicious hostname that, when processed, injects arbitrary shell commands into the --netrc-cmd argument [1][2][4].

Exploitation and

Attack Surface

Exploitation requires the user to have the --netrc-cmd option enabled in their command line or configuration, or to use the netrc_cmd parameter in Python scripts. The attacker must deliver a maliciously crafted URL to the victim, which can be done via a crafted webpage that triggers an HTTP redirect to a URL with a specially crafted hostname. While the malicious URL itself may appear suspicious, an inconspicuous redirect can make the attack covert. No authentication is needed beyond the user's existing yt-dlp setup, and the attacker does not need any special network position [2][4].

Impact

Successful exploitation allows an attacker to execute arbitrary commands on the user's system with the privileges of the yt-dlp process. This could lead to full system compromise, data theft, or further lateral movement. The impact is considered high for users who employ the --netrc-cmd option [2][4].

Mitigation

The vulnerability is fixed in yt-dlp version 2026.02.21, which validates all netrc "machine" values and raises an error upon unexpected input, preventing injection [3][4]. Users who cannot upgrade should avoid using --netrc-cmd or, at minimum, not include the placeholder {} in their command argument. No evidence of exploitation in the wild has been found [2][4].

AI Insight generated on May 19, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
yt-dlpPyPI
>= 2023.06.21, < 2026.02.212026.02.21

Affected products

2
  • Range: >=2023.06.21, <2026.02.21
  • yt-dlp/yt-dlpv5
    Range: >= 2023.06.21, < 2026.02.21

Patches

1
1fbbe29b99dc

[ie] Limit `netrc_machine` parameter to shell-safe characters

https://github.com/yt-dlp/yt-dlpSimon SawickiFeb 14, 2026via ghsa
5 files changed · +17 8
  • test/test_InfoExtractor.py+2 0 modified
    @@ -76,6 +76,8 @@ def test_get_netrc_login_info(self):
                 self.assertEqual(ie._get_netrc_login_info(netrc_machine='empty_pass'), ('user', ''))
                 self.assertEqual(ie._get_netrc_login_info(netrc_machine='both_empty'), ('', ''))
                 self.assertEqual(ie._get_netrc_login_info(netrc_machine='nonexistent'), (None, None))
    +            with self.assertRaises(ExtractorError):
    +                ie._get_netrc_login_info(netrc_machine=';echo rce')
     
         def test_html_search_regex(self):
             html = '<p id="foo">Watch this <a href="http://www.youtube.com/watch?v=BaW_jenozKc">video</a></p>'
    
  • yt_dlp/extractor/common.py+10 3 modified
    @@ -661,9 +661,11 @@ def initialize(self):
             if not self._ready:
                 self._initialize_pre_login()
                 if self.supports_login():
    -                username, password = self._get_login_info()
    -                if username:
    -                    self._perform_login(username, password)
    +                # try login only if it would actually do anything
    +                if type(self)._perform_login is not InfoExtractor._perform_login:
    +                    username, password = self._get_login_info()
    +                    if username:
    +                        self._perform_login(username, password)
                 elif self.get_param('username') and False not in (self.IE_DESC, self._NETRC_MACHINE):
                     self.report_warning(f'Login with password is not supported for this website. {self._login_hint("cookies")}')
                 self._real_initialize()
    @@ -1385,6 +1387,11 @@ def _html_search_regex(self, pattern, string, name, default=NO_DEFAULT, fatal=Tr
     
         def _get_netrc_login_info(self, netrc_machine=None):
             netrc_machine = netrc_machine or self._NETRC_MACHINE
    +        if not netrc_machine:
    +            raise ExtractorError(f'Missing netrc_machine and {type(self).__name__}._NETRC_MACHINE')
    +        ALLOWED = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-_'
    +        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)
     
             cmd = self.get_param('netrc_cmd')
             if cmd:
    
  • yt_dlp/extractor/getcourseru.py+1 1 modified
    @@ -59,7 +59,7 @@ class GetCourseRuIE(InfoExtractor):
             'marafon.mani-beauty.com',
             'on.psbook.ru',
         ]
    -    _BASE_URL_RE = rf'https?://(?:(?!player02\.)[^.]+\.getcourse\.(?:ru|io)|{"|".join(map(re.escape, _DOMAINS))})'
    +    _BASE_URL_RE = rf'https?://(?:(?!player02\.)[a-zA-Z0-9-]+\.getcourse\.(?:ru|io)|{"|".join(map(re.escape, _DOMAINS))})'
         _VALID_URL = [
             rf'{_BASE_URL_RE}/(?!pl/|teach/)(?P<id>[^?#]+)',
             rf'{_BASE_URL_RE}/(?:pl/)?teach/control/lesson/view\?(?:[^#]+&)?id=(?P<id>\d+)',
    
  • yt_dlp/extractor/pornhub.py+2 2 modified
    @@ -128,7 +128,7 @@ class PornHubIE(PornHubBaseIE):
         _VALID_URL = rf'''(?x)
                         https?://
                             (?:
    -                            (?:[^/]+\.)?
    +                            (?:[a-zA-Z0-9.-]+\.)?
                                 {PornHubBaseIE._PORNHUB_HOST_RE}
                                 /(?:(?:view_video\.php|video/show)\?viewkey=|embed/)|
                                 (?:www\.)?thumbzilla\.com/video/
    @@ -534,7 +534,7 @@ def _extract_entries(self, webpage, host):
     
     
     class PornHubUserIE(PornHubPlaylistBaseIE):
    -    _VALID_URL = rf'(?P<url>https?://(?:[^/]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/?#&]+))(?:[?#&]|/(?!videos)|$)'
    +    _VALID_URL = rf'(?P<url>https?://(?:[a-zA-Z0-9.-]+\.)?{PornHubBaseIE._PORNHUB_HOST_RE}/(?:(?:user|channel)s|model|pornstar)/(?P<id>[^/?#&]+))(?:[?#&]|/(?!videos)|$)'
         _TESTS = [{
             'url': 'https://www.pornhub.com/model/zoe_ph',
             'playlist_mincount': 118,
    
  • yt_dlp/extractor/teachable.py+2 2 modified
    @@ -102,7 +102,7 @@ class TeachableIE(TeachableBaseIE):
         _WORKING = False
         _VALID_URL = r'''(?x)
                         (?:
    -                        {}https?://(?P<site_t>[^/]+)|
    +                        {}https?://(?P<site_t>[a-zA-Z0-9.-]+)|
                             https?://(?:www\.)?(?P<site>{})
                         )
                         /courses/[^/]+/lectures/(?P<id>\d+)
    @@ -211,7 +211,7 @@ def _real_extract(self, url):
     class TeachableCourseIE(TeachableBaseIE):
         _VALID_URL = r'''(?x)
                             (?:
    -                            {}https?://(?P<site_t>[^/]+)|
    +                            {}https?://(?P<site_t>[a-zA-Z0-9.-]+)|
                                 https?://(?:www\.)?(?P<site>{})
                             )
                             /(?:courses|p)/(?:enrolled/)?(?P<id>[^/?#&]+)
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.