mirror of
https://github.com/unshackle-dl/unshackle.git
synced 2026-03-11 17:09:00 +00:00
fix(service): redact proxy credentials in logs
Proxy URIs may contain embedded userinfo (username/password). Add a small sanitizer helper and use it for proxy mapping and proxy selection logs to avoid leaking credentials.
This commit is contained in:
@@ -5,7 +5,7 @@ from collections.abc import Generator
|
||||
from http.cookiejar import CookieJar
|
||||
from pathlib import Path
|
||||
from typing import Optional, Union
|
||||
from urllib.parse import urlparse
|
||||
from urllib.parse import urlparse, urlunparse
|
||||
|
||||
import click
|
||||
import m3u8
|
||||
@@ -27,6 +27,45 @@ from unshackle.core.tracks import Chapters, Tracks
|
||||
from unshackle.core.utilities import get_cached_ip_info, get_ip_info
|
||||
|
||||
|
||||
def sanitize_proxy_for_log(uri: Optional[str]) -> Optional[str]:
|
||||
"""
|
||||
Sanitize a proxy URI for logs by redacting any embedded userinfo (username/password).
|
||||
|
||||
Examples:
|
||||
- http://user:pass@host:8080 -> http://REDACTED@host:8080
|
||||
- socks5h://user@host:1080 -> socks5h://REDACTED@host:1080
|
||||
"""
|
||||
if uri is None:
|
||||
return None
|
||||
if not isinstance(uri, str):
|
||||
return str(uri)
|
||||
if not uri:
|
||||
return uri
|
||||
|
||||
try:
|
||||
parsed = urlparse(uri)
|
||||
|
||||
# Handle schemeless proxies like "user:pass@host:port"
|
||||
if not parsed.scheme and not parsed.netloc and "@" in uri and "://" not in uri:
|
||||
# Parse as netloc using a dummy scheme, then strip scheme back out.
|
||||
dummy = urlparse(f"http://{uri}")
|
||||
netloc = dummy.netloc
|
||||
if "@" in netloc:
|
||||
netloc = f"REDACTED@{netloc.split('@', 1)[1]}"
|
||||
# urlparse("http://...") sets path to "" for typical netloc-only strings; keep it just in case.
|
||||
return f"{netloc}{dummy.path}"
|
||||
|
||||
netloc = parsed.netloc
|
||||
if "@" in netloc:
|
||||
netloc = f"REDACTED@{netloc.split('@', 1)[1]}"
|
||||
|
||||
return urlunparse(parsed._replace(netloc=netloc))
|
||||
except Exception:
|
||||
if "@" in uri:
|
||||
return f"REDACTED@{uri.split('@', 1)[1]}"
|
||||
return uri
|
||||
|
||||
|
||||
class Service(metaclass=ABCMeta):
|
||||
"""The Service Base Class."""
|
||||
|
||||
@@ -75,7 +114,9 @@ class Service(metaclass=ABCMeta):
|
||||
# Check if there's a mapping for this query
|
||||
mapped_value = proxy_map.get(full_proxy_key)
|
||||
if mapped_value:
|
||||
self.log.info(f"Found service-specific proxy mapping: {full_proxy_key} -> {mapped_value}")
|
||||
self.log.info(
|
||||
f"Found service-specific proxy mapping: {full_proxy_key} -> {sanitize_proxy_for_log(mapped_value)}"
|
||||
)
|
||||
# Query the proxy provider with the mapped value
|
||||
if proxy_provider_name:
|
||||
# Specific provider requested
|
||||
@@ -87,9 +128,13 @@ class Service(metaclass=ABCMeta):
|
||||
mapped_proxy_uri = proxy_provider.get_proxy(mapped_value)
|
||||
if mapped_proxy_uri:
|
||||
proxy = mapped_proxy_uri
|
||||
self.log.info(f"Using mapped proxy from {proxy_provider.__class__.__name__}: {proxy}")
|
||||
self.log.info(
|
||||
f"Using mapped proxy from {proxy_provider.__class__.__name__}: {sanitize_proxy_for_log(proxy)}"
|
||||
)
|
||||
else:
|
||||
self.log.warning(f"Failed to get proxy for mapped value '{mapped_value}', using default")
|
||||
self.log.warning(
|
||||
f"Failed to get proxy for mapped value '{sanitize_proxy_for_log(mapped_value)}', using default"
|
||||
)
|
||||
else:
|
||||
self.log.warning(f"Proxy provider '{proxy_provider_name}' not found, using default proxy")
|
||||
else:
|
||||
@@ -98,10 +143,14 @@ class Service(metaclass=ABCMeta):
|
||||
mapped_proxy_uri = proxy_provider.get_proxy(mapped_value)
|
||||
if mapped_proxy_uri:
|
||||
proxy = mapped_proxy_uri
|
||||
self.log.info(f"Using mapped proxy from {proxy_provider.__class__.__name__}: {proxy}")
|
||||
self.log.info(
|
||||
f"Using mapped proxy from {proxy_provider.__class__.__name__}: {sanitize_proxy_for_log(proxy)}"
|
||||
)
|
||||
break
|
||||
else:
|
||||
self.log.warning(f"No provider could resolve mapped value '{mapped_value}', using default")
|
||||
self.log.warning(
|
||||
f"No provider could resolve mapped value '{sanitize_proxy_for_log(mapped_value)}', using default"
|
||||
)
|
||||
|
||||
if not proxy:
|
||||
# don't override the explicit proxy set by the user, even if they may be geoblocked
|
||||
|
||||
Reference in New Issue
Block a user