fix: let the download reach the PO token provider (closes #1064)

The image ships yt-dlp's bgutil PO token provider and starts it on
loopback, where the plugin dials it at http://127.0.0.1:4416. Since
482381d scoped the connect-time allowance to the configured proxy, the
download subprocess could no longer resolve it:

    Refusing to connect to non-global address for host '127.0.0.1'

which surfaces as the plugin's "Error reaching GET .../ping". Metadata
extraction runs in the main process and installs no guard, so titles kept
resolving while the download itself ran without a token — and YouTube
increasingly answers those with 403.

The allowance already had the right shape for this; it was just named for
its only user. Endpoints the operator or the image configured are now
allowed as a class: install_socket_guard takes service_urls alongside
proxy_urls, and ytdl derives them from the bundled default plus any
base_url set through the youtubepot-bgutilhttp (or the deprecated youtube
getpot_bgutil_baseurl) extractor argument. The bundled server runs either
way, so it stays allowed when a base URL is configured.

Matching stays exact host:port on the configured string, so nothing else
on loopback opens up: a hostile media URL naming the endpoint reaches a
token server with two endpoints and nothing worth reading.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Alex Shnitman
2026-08-18 15:43:14 +02:00
parent ac46fff6d9
commit f3c464fad5
4 changed files with 202 additions and 78 deletions
+40
View File
@@ -77,6 +77,7 @@ from ytdl import (
MusicMetadataPreProcessor,
_compact_persisted_entry,
_convert_srt_to_txt_file,
_pot_provider_urls,
_AlbumArtistPostProcessor,
_resolve_outtmpl_fields,
_sanitize_entry_for_pickle,
@@ -1071,5 +1072,44 @@ class ShortTitleForFailedUrlTests(unittest.TestCase):
self.assertEqual(_short_title_for_failed_url(malformed), malformed)
class PotProviderUrlsTests(unittest.TestCase):
"""#1064: the connect-time guard must let the download reach the PO token
provider, so it has to know every endpoint yt-dlp might dial for one."""
def test_bundled_provider_by_default(self):
self.assertEqual(_pot_provider_urls({}), ("http://127.0.0.1:4416",))
def test_configured_base_url_is_added(self):
urls = _pot_provider_urls({
"extractor_args": {"youtubepot-bgutilhttp": {"base_url": ["http://pot:4416"]}},
})
# The bundled server runs regardless, so both stay reachable.
self.assertEqual(urls, ("http://127.0.0.1:4416", "http://pot:4416"))
def test_deprecated_base_url_arg_is_honoured(self):
urls = _pot_provider_urls({
"extractor_args": {"youtube": {"getpot_bgutil_baseurl": ["http://pot:4416"]}},
})
self.assertEqual(urls, ("http://127.0.0.1:4416", "http://pot:4416"))
def test_unrelated_extractor_args_are_ignored(self):
urls = _pot_provider_urls({
"extractor_args": {"youtube": {"player_client": ["web"]}},
})
self.assertEqual(urls, ("http://127.0.0.1:4416",))
def test_malformed_extractor_args_do_not_raise(self):
# YTDL_OPTIONS is operator-supplied JSON and reaches here unvalidated.
for opts in (
{"extractor_args": None},
{"extractor_args": "youtube:player_client=web"},
{"extractor_args": {"youtubepot-bgutilhttp": "http://pot:4416"}},
{"extractor_args": {"youtubepot-bgutilhttp": {"base_url": []}}},
):
with self.subTest(opts=opts):
self.assertEqual(_pot_provider_urls(opts), ("http://127.0.0.1:4416",))
if __name__ == "__main__":
unittest.main()