diff --git a/docs/guide/proxies-and-vpn.md b/docs/guide/proxies-and-vpn.md index 89c8f627..542cd2cf 100644 --- a/docs/guide/proxies-and-vpn.md +++ b/docs/guide/proxies-and-vpn.md @@ -198,6 +198,8 @@ file and line, to the service maintainer. A request to your own machine or to your local network gives no warning. This includes a private IP address such as `192.168.1.50`, and a `.local`, `.lan` or `.home.arpa` host name. +A request to the `host` of a remote CDM in `remote_cdm` also gives no warning. The CDM API +sees your own IP, but the service does not. A `socks5://` proxy makes the DNS lookup of each host on your machine and gives no warning. Use `socks5h://` to make the proxy do the DNS lookups too. diff --git a/tests/core/test_run_proxy.py b/tests/core/test_run_proxy.py index 4757de65..6691e4ba 100644 --- a/tests/core/test_run_proxy.py +++ b/tests/core/test_run_proxy.py @@ -84,6 +84,16 @@ def test_proxy_and_local_hosts_do_not_warn(caplog, host): assert leaks(caplog) == [] +def test_remote_cdm_host_does_not_warn(caplog, monkeypatch): + """A service calls its remote CDM itself, so no core frame ends the walk; the CDM API is not the service.""" + from unshackle.core.config import config + + monkeypatch.setattr(config, "remote_cdm", [{"name": "pr", "host": "https://cdm.example/playready"}]) + with caplog.at_level(logging.WARNING): + lookup("cdm.example") + assert leaks(caplog) == [] + + @pytest.mark.parametrize("host", ["8.8.8.8", "2606:4700:4700::1111", "api.example"]) def test_public_hosts_warn(caplog, host): with caplog.at_level(logging.WARNING): diff --git a/unshackle/core/utils/run_proxy.py b/unshackle/core/utils/run_proxy.py index cb8daf7c..692b3a36 100644 --- a/unshackle/core/utils/run_proxy.py +++ b/unshackle/core/utils/run_proxy.py @@ -134,6 +134,13 @@ def fallback_session() -> requests.Session: return session +def cdm_hosts() -> set[str]: + """The hosts of the remote CDMs in the config: a CDM API of the user, which a service calls direct.""" + from unshackle.core.config import config + + return {hostname(str(x.get("host") or x.get("Host") or "")) for x in config.remote_cdm} + + def is_local(host: str) -> bool: """True for this machine and the local network, which never see the internet IP of the user.""" if host in ("localhost", "localhost.", "localhost.localdomain"): @@ -177,7 +184,7 @@ def check(host: Any, frame: Optional[FrameType]) -> None: if isinstance(host, bytes): host = host.decode(errors="replace") host = str(host or "").lower().strip("[]") - if not host or is_local(host) or host in proxy_hosts: + if not host or is_local(host) or host in proxy_hosts or host in cdm_hosts(): return found = find_service(frame, through_core=False) if not found: