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
115 lines
4.9 KiB
Python
115 lines
4.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
|
|
|
|
"""Deterministic consistency guards for the model-load security gate.
|
|
|
|
The gate spans many parallel sites (validate/load/status, the inference/training/export
|
|
workers, the preflight route); past regressions were a fix at one site with a sibling
|
|
left behind. These guards enumerate the sites mechanically (AST + source) so a new site
|
|
that drops the token or mis-reports the requirement fails here, not in a later review.
|
|
"""
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
_BACKEND = Path(__file__).resolve().parent.parent
|
|
|
|
# Probes read Hub config to classify a model; a token-less call 404s on a gated repo.
|
|
# Scan callers under routes/ and core/ (probe definitions live in utils/).
|
|
_PROBE_FUNCS = {"is_vision_model", "is_embedding_model", "detect_audio_type"}
|
|
_PROBE_CALLER_ROOTS = ("routes", "core")
|
|
|
|
|
|
def _iter_caller_files():
|
|
for root in _PROBE_CALLER_ROOTS:
|
|
yield from (_BACKEND / root).rglob("*.py")
|
|
|
|
|
|
def _passes_token(call: ast.Call) -> bool:
|
|
"""True if the call passes an hf_token (keyword, or the 2nd positional slot)."""
|
|
if any(kw.arg in ("hf_token", "token") for kw in call.keywords if kw.arg is not None):
|
|
return True
|
|
return len(call.args) >= 2
|
|
|
|
|
|
def _call_name(call: ast.Call):
|
|
fn = call.func
|
|
return fn.id if isinstance(fn, ast.Name) else getattr(fn, "attr", None)
|
|
|
|
|
|
def test_capability_probes_thread_the_hf_token():
|
|
"""Every capability-probe caller passes the token; a token-less probe misclassifies
|
|
a gated model (the /check-vision regression)."""
|
|
offenders = []
|
|
for path in _iter_caller_files():
|
|
try:
|
|
tree = ast.parse(path.read_text())
|
|
except SyntaxError:
|
|
continue
|
|
for node in ast.walk(tree):
|
|
if isinstance(node, ast.Call) and _call_name(node) in _PROBE_FUNCS:
|
|
if not _passes_token(node):
|
|
rel = path.relative_to(_BACKEND)
|
|
offenders.append(f"{rel}:{node.lineno} {_call_name(node)}() drops the hf_token")
|
|
assert not offenders, (
|
|
"A capability probe must pass the hf_token so gated/private models classify "
|
|
"correctly:\n " + "\n ".join(offenders)
|
|
)
|
|
|
|
|
|
def test_gguf_trust_remote_code_reported_inert_not_from_yaml():
|
|
"""GGUF never executes auto_map, so requires_trust_remote_code is reported via the
|
|
resolver or False, never the raw YAML bool() (the round-6 regression)."""
|
|
src = (_BACKEND / "routes" / "inference.py").read_text()
|
|
assert "requires_trust_remote_code = bool(" not in src, (
|
|
"Report requires_trust_remote_code via _resolve_loaded_trust_remote_code "
|
|
"(non-GGUF) or set it False (GGUF); never bool(inference_config.get(...))."
|
|
)
|
|
|
|
|
|
def test_capability_detection_caches_are_token_aware():
|
|
"""Every capability cache is keyed by (model, token_fingerprint) so an unauthenticated
|
|
miss cannot poison a later authenticated lookup (the audio-cache regression)."""
|
|
src = (_BACKEND / "utils" / "models" / "model_config.py").read_text()
|
|
offenders = []
|
|
for line in src.splitlines():
|
|
stripped = line.strip()
|
|
if "_detection_cache:" in stripped and stripped.endswith("= {}"):
|
|
if "Dict[Tuple" not in stripped and "Dict[tuple" not in stripped:
|
|
offenders.append(stripped)
|
|
assert not offenders, (
|
|
"A capability cache must be keyed by (model, token_fingerprint), not the bare "
|
|
"model name:\n " + "\n ".join(offenders)
|
|
)
|
|
|
|
|
|
def test_malware_and_consent_gates_cover_the_lora_base():
|
|
"""Every worker that runs a load gate also resolves the LoRA base, so a poisoned or
|
|
custom-code base is never skipped."""
|
|
gated_workers = [
|
|
"core/inference/worker.py",
|
|
"core/export/worker.py",
|
|
"core/training/worker.py",
|
|
]
|
|
offenders = []
|
|
for rel in gated_workers:
|
|
src = (_BACKEND / rel).read_text()
|
|
runs_gate = "evaluate_file_security(" in src or "evaluate_remote_code_consent" in src
|
|
resolves_base = "get_base_model_from_lora_identifier(" in src or "base_model" in src
|
|
if runs_gate and not resolves_base:
|
|
offenders.append(f"{rel} runs a load gate but never resolves the LoRA base")
|
|
assert not offenders, "\n".join(offenders)
|
|
|
|
|
|
def test_rag_embedding_path_runs_the_malware_gate():
|
|
"""The RAG embedding model is set through /settings and later loaded by
|
|
SentenceTransformer, which deserializes pickles; both sites must run the malware gate
|
|
or a flagged repo loads unscanned (bypassing the normal model-load protections)."""
|
|
offenders = []
|
|
for rel in ("routes/settings.py", "core/rag/embeddings.py"):
|
|
if "evaluate_file_security(" not in (_BACKEND / rel).read_text():
|
|
offenders.append(
|
|
f"{rel} loads/persists an embedding model without evaluate_file_security"
|
|
)
|
|
assert not offenders, "\n".join(offenders)
|