* Studio: make the startup public-IP lookup opt-in (#7307 P8) Startup resolved the machine's external IP by asking ifconfig.me whenever Studio bound to 0.0.0.0 or ::. That tells whoever runs that service this host is running Unsloth, which the user never agreed to. The lookup is now gated behind UNSLOTH_STUDIO_PUBLIC_IP_PROBE, off by default, and logs plainly what it sends and where when enabled. Only 1/true/yes/on enable it, so a typo leaves the private default in place. The other two steps stay unconditional because neither discloses anything: the GCE metadata server is link-local, and the UDP connect only asks the kernel which local address routes to 8.8.8.8 without putting a packet on the wire. Disabling the probe therefore still yields a usable LAN address for the access banner. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: gate the check-host.net reachability probe too, and keep the cloud address Problem 8 of #7307 is the check-host.net probe in _verify_global_reachability: it hands this machine's address and port to a third party and asks its nodes to connect back. That was still unconditional, so on GCE and on any host whose routing address is already public the reported behaviour was unchanged. It is now behind the same UNSLOTH_STUDIO_PUBLIC_IP_PROBE opt-in. A private interface address is not proof the port is unreachable, since a NAT or cloud firewall can forward it. The private-address path no longer sets _public_reachable = False, so the banner keeps its warning instead of claiming local network only. To stop the privacy default from costing cloud users their shareable address, step 1 now reads AWS IMDSv2 and Azure IMDS alongside GCE on link-local 169.254.169.254. Also drops the redundant function-local import os and documents the variable in the README remote access section. * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Studio: switch to a single UNSLOTH_STUDIO_DISABLE_PUBLIC_CHECK opt-out Replaces the opt-in variable and the cloud metadata work from the previous commit. Both third-party startup lookups stay on by default, so nothing changes for existing users, and one variable turns both off for lab and privacy-sensitive deployments. That is the fallback the reporter offered in #7307 Problem 8, and it keeps the firewall diagnostic that the reachability check exists to provide. The ifconfig.me lookup and the check-host.net probe are now both guarded by public_check_disabled(). Parsing matches the nearest existing switch, _trust_forwarded_for in utils/client_ip.py. The reachability guard sits after the private-address branch, which makes no network call, so a LAN user who opts out still gets the address note. --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> Co-authored-by: danielhanchen <danielhanchen@gmail.com>
103 lines
3.2 KiB
Python
103 lines
3.2 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Coverage for UNSLOTH_STUDIO_DISABLE_PUBLIC_CHECK (#7307 Problem 8).
|
|
|
|
A wildcard bind asks ifconfig.me for the public IP and check-host.net whether the
|
|
port is reachable. Both stay on by default; setting the var skips both, which is
|
|
what lab and privacy-sensitive deployments asked for.
|
|
"""
|
|
|
|
import socket
|
|
import urllib.request
|
|
|
|
import pytest
|
|
|
|
import run
|
|
from run import (
|
|
DISABLE_PUBLIC_CHECK_ENV,
|
|
_resolve_external_ip,
|
|
_verify_global_reachability,
|
|
public_check_disabled,
|
|
)
|
|
|
|
IFCONFIG = "https://ifconfig.me"
|
|
CHECK_HOST = "check-host.net"
|
|
|
|
|
|
class _FakeSocket:
|
|
"""Stand-in for the step 3 UDP route lookup."""
|
|
|
|
def connect(self, addr):
|
|
pass
|
|
|
|
def getsockname(self):
|
|
return ("192.168.1.50", 0)
|
|
|
|
def close(self):
|
|
pass
|
|
|
|
|
|
@pytest.fixture
|
|
def calls(monkeypatch):
|
|
"""Record every outbound URL and fail it, so resolution reaches the LAN step."""
|
|
seen = []
|
|
|
|
def _urlopen(req, *args, **kwargs):
|
|
seen.append(req if isinstance(req, str) else req.full_url)
|
|
raise OSError("no network in this test")
|
|
|
|
monkeypatch.setattr(urllib.request, "urlopen", _urlopen)
|
|
monkeypatch.setattr(socket, "socket", lambda *a, **k: _FakeSocket())
|
|
monkeypatch.delenv(DISABLE_PUBLIC_CHECK_ENV, raising = False)
|
|
return seen
|
|
|
|
|
|
# ── public_check_disabled ───────────────────────────────────────────
|
|
|
|
|
|
def test_enabled_by_default(monkeypatch):
|
|
monkeypatch.delenv(DISABLE_PUBLIC_CHECK_ENV, raising = False)
|
|
assert public_check_disabled() is False
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ["1", "true", "TRUE", "Yes", " 1 "])
|
|
def test_disabling_values(monkeypatch, raw):
|
|
monkeypatch.setenv(DISABLE_PUBLIC_CHECK_ENV, raw)
|
|
assert public_check_disabled() is True
|
|
|
|
|
|
@pytest.mark.parametrize("raw", ["0", "false", "no", "off", "", " ", "ture"])
|
|
def test_anything_else_leaves_it_on(monkeypatch, raw):
|
|
monkeypatch.setenv(DISABLE_PUBLIC_CHECK_ENV, raw)
|
|
assert public_check_disabled() is False
|
|
|
|
|
|
# ── the two lookups ─────────────────────────────────────────────────
|
|
|
|
|
|
def test_public_ip_lookup_runs_by_default(calls):
|
|
assert _resolve_external_ip() == "192.168.1.50"
|
|
assert IFCONFIG in calls
|
|
|
|
|
|
def test_public_ip_lookup_skipped_when_disabled(monkeypatch, calls):
|
|
monkeypatch.setenv(DISABLE_PUBLIC_CHECK_ENV, "1")
|
|
|
|
assert _resolve_external_ip() == "192.168.1.50", "the LAN address still resolves"
|
|
assert IFCONFIG not in calls
|
|
|
|
|
|
def test_reachability_probe_runs_by_default(calls):
|
|
_verify_global_reachability("95.216.11.2", 8888)
|
|
assert any(CHECK_HOST in url for url in calls)
|
|
|
|
|
|
def test_reachability_probe_skipped_when_disabled(monkeypatch, calls, capsys):
|
|
monkeypatch.setenv(DISABLE_PUBLIC_CHECK_ENV, "1")
|
|
|
|
_verify_global_reachability("95.216.11.2", 8888)
|
|
capsys.readouterr()
|
|
|
|
assert not any(CHECK_HOST in url for url in calls)
|
|
assert run._public_reachable is None, "skipping must not claim a reachability result"
|