Files
unslothai--unsloth/studio/backend/tests/test_secure_tunnel_gate.py
T
wehub-resource-sync e93507a09c
Lockfile supply-chain audit / lockfile supply-chain audit (push) Has been cancelled
Windows Studio GGUF CI / GPU prebuilt resolves without Visual Studio (push) Has been cancelled
Windows Studio GGUF CI / setup.ps1 unit tests (VS 2026 / CMake guard) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2022) (push) Has been cancelled
Windows Studio GGUF CI / real-VS detection (VS 2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-2025-vs2026) (push) Has been cancelled
Windows Studio GGUF CI / VC++ runtime detect + install round-trip (windows-latest) (push) Has been cancelled
Windows Studio Update CI / Studio Updating Tests (push) Has been cancelled
Wheel CI / Wheel build + content sanity + import smoke (push) Has been cancelled
Lint CI / Source lint (Python + shell + YAML + JSON + safety nets) (push) Has been cancelled
MLX CI on Mac M1 / dispatch (push) Has been cancelled
Security audit / advisory audit (pip + npm + cargo) (push) Has been cancelled
Security audit / pip scan-packages :: extras (push) Has been cancelled
Security audit / pip scan-packages :: studio (push) Has been cancelled
Security audit / pip scan-packages :: hf-stack (push) Has been cancelled
Security audit / npm scan-packages (Studio frontend tarballs) (push) Has been cancelled
Security audit / workflow-trigger lint (pull_request_target / cache-poisoning) (push) Has been cancelled
Security audit / pytest tests/security (push) Has been cancelled
Security audit / npm provenance + new install-script diff (push) Has been cancelled
Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Backend CI / (Python 3.10) (push) Has been cancelled
Backend CI / (Python 3.11) (push) Has been cancelled
Backend CI / (Python 3.12) (push) Has been cancelled
Backend CI / (Python 3.13) (push) Has been cancelled
Backend CI / Repo tests (CPU) (push) Has been cancelled
Frontend CI / Frontend build + bundle sanity (push) Has been cancelled
Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Mac Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Mac Studio GGUF CI / JSON, images (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-14) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26) (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-15-intel) (push) Has been cancelled
Mac Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Mac Studio Install Matrix CI / Install + load (macos-26-intel) (push) Has been cancelled
Mac Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Tauri CI / Tauri Linux debug build (no codesign) (push) Has been cancelled
Mac Studio Update CI / Studio Updating Tests (push) Has been cancelled
Studio UI CI / Chat UI Tests (push) Has been cancelled
Windows Studio API CI / Studio API & Auth Tests (push) Has been cancelled
Windows Studio UI CI / Chat UI Tests (push) Has been cancelled
Studio Update CI / Studio Updating Tests (push) Has been cancelled
Core / Core (HF=default + TRL=default) (push) Has been cancelled
Core / Core (HF=4.57.6 + TRL<1) (push) Has been cancelled
Core / Core (HF=latest + TRL=latest) (push) Has been cancelled
Core / llama.cpp build + smoke (push) Has been cancelled
Windows Studio GGUF CI / OpenAI, Anthropic API tests (push) Has been cancelled
Windows Studio GGUF CI / Tool calling Tests (push) Has been cancelled
Windows Studio GGUF CI / JSON, images (push) Has been cancelled
Windows Studio GGUF CI / Studio install + inference without Visual Studio (push) Has been cancelled
Studio export capability / capability (macos-latest) (push) Has been cancelled
Studio export capability / capability (ubuntu-latest) (push) Has been cancelled
Studio export capability / capability (windows-latest) (push) Has been cancelled
Cross-platform parity / parity (macos-latest) (push) Has been cancelled
Cross-platform parity / parity (windows-latest) (push) Has been cancelled
Scorecard supply-chain security / Scorecard analysis (push) Has been cancelled
Studio load-orchestrator CI / test (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:59:56 +08:00

215 lines
7.9 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
"""Cloudflare tunnel start gate, incl. --secure on loopback. Imports run.py
directly, so run under the Studio venv."""
from __future__ import annotations
import sys
from pathlib import Path
import pytest
_BACKEND = Path(__file__).resolve().parents[1]
if str(_BACKEND) not in sys.path:
sys.path.insert(0, str(_BACKEND))
from run import _cloudflare_tunnel_should_start as should_start # noqa: E402
@pytest.mark.parametrize(
"cloudflare,host,secure,api_only,is_colab,expected",
[
# Non-secure wildcard binds tunnel by default.
(True, "0.0.0.0", False, False, False, True),
(True, "::", False, False, False, True),
(True, "127.0.0.1", False, False, False, False),
(True, "localhost", False, False, False, False),
# --secure tunnels a loopback bind too.
(True, "127.0.0.1", True, False, False, True),
(True, "0.0.0.0", True, False, False, True),
# --no-cloudflare always wins.
(False, "0.0.0.0", False, False, False, False),
(False, "::", False, False, False, False),
(False, "127.0.0.1", True, False, False, False),
# Non-secure api-only never tunnels (Tauri).
(True, "0.0.0.0", False, True, False, False),
(True, "::", False, True, False, False),
# --secure tunnels even api-only (headless secure API server).
(True, "127.0.0.1", True, True, False, True),
# Colab never tunnels, even --secure.
(True, "0.0.0.0", False, False, True, False),
(True, "::", False, False, True, False),
(True, "127.0.0.1", True, False, True, False),
(True, "127.0.0.1", True, True, True, False),
],
)
def test_cloudflare_gate(cloudflare, host, secure, api_only, is_colab, expected):
assert (
should_start(
cloudflare = cloudflare,
host = host,
secure = secure,
api_only = api_only,
is_colab = is_colab,
)
is expected
)
def test_run_server_accepts_secure_kwarg():
import inspect
import run
assert "secure" in inspect.signature(run.run_server).parameters
assert inspect.signature(run.run_server).parameters["secure"].default is False
def test_arg_parser_secure_polarity_and_not_secure_alias():
# --secure/--no-secure is the documented flag; --not-secure is a hidden,
# back-compat alias for --no-secure. Last flag wins (BooleanOptionalAction).
import run
parser = run._build_arg_parser()
assert parser.parse_args([]).secure is False
assert parser.parse_args(["--secure"]).secure is True
assert parser.parse_args(["--no-secure"]).secure is False
assert parser.parse_args(["--not-secure"]).secure is False
assert parser.parse_args(["--secure", "--not-secure"]).secure is False
assert parser.parse_args(["--not-secure", "--secure"]).secure is True
def test_run_server_accepts_enable_tools_kwarg():
import inspect
import run
params = inspect.signature(run.run_server).parameters
assert "enable_tools" in params
assert params["enable_tools"].default is None # default: leave policy unset
def test_tool_policy_not_auto_disabled_by_bind():
# Tools default on for every bind; the backend only changes the policy from
# an explicit --enable-tools/--disable-tools, never from host/secure.
import run
from state.tool_policy import get_tool_policy, reset_tool_policy
for host in ("127.0.0.1", "localhost", "0.0.0.0"):
reset_tool_policy()
run._apply_cli_tool_policy(None) # no flag, on any bind
assert get_tool_policy() is None, host # untouched: per-request honored
reset_tool_policy()
run._apply_cli_tool_policy(True) # --enable-tools: forced on
assert get_tool_policy() is True
reset_tool_policy()
run._apply_cli_tool_policy(False) # --disable-tools: forced off
assert get_tool_policy() is False
reset_tool_policy()
def test_tool_policy_notice_wording():
# The plain-server startup banner states the resolved policy for every bind.
import run
loopback = run._tool_policy_notice("127.0.0.1", False, None)
assert "ENABLED by default" in loopback and "loopback" in loopback
network = run._tool_policy_notice("0.0.0.0", False, None)
assert "ENABLED by default" in network and "network-reachable" in network
secure = run._tool_policy_notice("127.0.0.1", True, None)
assert "Cloudflare HTTPS tunnel" in secure
assert run._tool_policy_notice("0.0.0.0", False, False) == (
"Server-side tools are DISABLED (--disable-tools)."
)
assert "ENABLED (--enable-tools)" in run._tool_policy_notice("0.0.0.0", False, True)
def test_startup_output_emits_tool_notice_on_network_bind(capsys, monkeypatch):
# Plain `unsloth studio -H 0.0.0.0` must not be silent about tools now.
import run
monkeypatch.setattr(run, "_verify_global_reachability", lambda *a, **k: None)
monkeypatch.setattr(run, "_print_cloudflare_line", lambda *a, **k: None)
monkeypatch.setattr(run, "_localhost_ipv6_mismatch_url", lambda *a, **k: None)
run._emit_startup_output("0.0.0.0", 8000, "0.0.0.0", secure = False, enable_tools = None)
out = capsys.readouterr().out
assert "Server-side tools" in out
assert "network-reachable" in out
def test_startup_output_emits_disabled_notice(capsys, monkeypatch):
import run
monkeypatch.setattr(run, "_localhost_ipv6_mismatch_url", lambda *a, **k: None)
run._emit_startup_output("127.0.0.1", 8000, "127.0.0.1", secure = False, enable_tools = False)
out = capsys.readouterr().out
assert "Server-side tools are DISABLED" in out
def test_run_server_rejects_secure_without_cloudflare():
# Direct backend callers (not just the CLI) must reject the contradictory combo.
import run
with pytest.raises(SystemExit) as exc:
run.run_server(secure = True, cloudflare = False)
assert "A secure Cloudflare link is not allowed" in str(exc.value)
def test_failclosed_message_present_in_source():
# The exact, user-facing fail-closed message must not drift.
src = (_BACKEND / "run.py").read_text(encoding = "utf-8")
assert (
"A secure Cloudflare link is not allowed, use --no-secure which provides a 0.0.0.0 link"
in src
)
@pytest.mark.parametrize(
"api_only,secure,expected",
[
(False, False, ["*"]), # plain server: any origin
(False, True, ["*"]), # secure UI server: any origin
(True, True, ["*"]), # secure api-only: remote browsers need any origin
(True, False, "tauri"), # local api-only: locked to the Tauri app
],
)
def test_cors_origins_for_mode(api_only, secure, expected):
from utils.host_policy import cors_origins_for_mode
origins = cors_origins_for_mode(api_only = api_only, secure = secure)
if expected == "tauri":
assert origins != ["*"] and any(o.startswith("tauri://") for o in origins)
else:
assert origins == expected
def test_run_server_exports_secure_env_for_cors():
# run_server must export UNSLOTH_SECURE before importing main so the CORS
# profile can tell remote secure serving from local Tauri use.
src = (_BACKEND / "run.py").read_text(encoding = "utf-8")
assert 'os.environ["UNSLOTH_SECURE"] = "1"' in src
def test_run_server_emit_tauri_port_defaults_on():
# Default on keeps the desktop app's stdout contract; the headless
# `run --api-only` path opts out explicitly.
import inspect
import run
params = inspect.signature(run.run_server).parameters
assert "emit_tauri_port" in params
assert params["emit_tauri_port"].default is True
def test_tauri_port_print_is_gated_in_source():
# The TAURI_PORT line must depend on emit_tauri_port, not api_only alone.
src = (_BACKEND / "run.py").read_text(encoding = "utf-8")
assert "if api_only and emit_tauri_port:" in src