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
90 lines
3.6 KiB
Python
90 lines
3.6 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
from pathlib import Path
|
|
|
|
|
|
SAVE_PY = Path(__file__).resolve().parents[2] / "unsloth" / "save.py"
|
|
|
|
|
|
def _get_function(source: str, function_name: str) -> ast.FunctionDef:
|
|
tree = ast.parse(source, filename = str(SAVE_PY))
|
|
for node in tree.body:
|
|
if isinstance(node, ast.FunctionDef) and node.name == function_name:
|
|
return node
|
|
raise AssertionError(f"Function {function_name} not found in save.py")
|
|
|
|
|
|
def _popen_calls(node: ast.AST) -> list[ast.Call]:
|
|
calls = []
|
|
for child in ast.walk(node):
|
|
if (
|
|
isinstance(child, ast.Call)
|
|
and isinstance(child.func, ast.Attribute)
|
|
and child.func.attr == "Popen"
|
|
and isinstance(child.func.value, ast.Name)
|
|
and child.func.value.id == "subprocess"
|
|
):
|
|
calls.append(child)
|
|
return calls
|
|
|
|
|
|
def _list_assignments(node: ast.AST, target: str) -> list[ast.List]:
|
|
lists = []
|
|
for child in ast.walk(node):
|
|
if isinstance(child, ast.Assign) and isinstance(child.value, ast.List):
|
|
if any(isinstance(t, ast.Name) and t.id == target for t in child.targets):
|
|
lists.append(child.value)
|
|
return lists
|
|
|
|
|
|
def test_lora_gguf_conversion_does_not_use_shell() -> None:
|
|
"""The LoRA -> GGUF conversion must pass argv as a list (no shell=True), so a crafted
|
|
save path cannot inject shell commands. The conversion lives in the shared helper now."""
|
|
helper = _get_function(SAVE_PY.read_text(encoding = "utf-8"), "_unsloth_save_lora_gguf")
|
|
popen_calls = _popen_calls(helper)
|
|
assert popen_calls, "Expected at least one subprocess.Popen call in _unsloth_save_lora_gguf"
|
|
|
|
for call in popen_calls:
|
|
shell = [
|
|
kw
|
|
for kw in call.keywords
|
|
if kw.arg == "shell" and isinstance(kw.value, ast.Constant) and kw.value.value is True
|
|
]
|
|
assert not shell, "subprocess.Popen must not use shell=True"
|
|
|
|
assert call.args, "subprocess.Popen must receive argv as a positional argument"
|
|
argv = call.args[0]
|
|
if isinstance(argv, ast.List):
|
|
elts = argv.elts
|
|
else:
|
|
# argv is built as a list variable (cmd = [...]) and passed positionally.
|
|
assert isinstance(argv, ast.Name), "argv must be a list or a list-built variable"
|
|
assigned = _list_assignments(helper, argv.id)
|
|
assert assigned, f"argv variable '{argv.id}' must be assigned a list literal"
|
|
elts = assigned[0].elts
|
|
|
|
assert len(elts) >= 2, "argv must include the interpreter and the converter script"
|
|
first = elts[0]
|
|
assert (
|
|
isinstance(first, ast.Attribute) and first.attr == "executable"
|
|
), "argv[0] should be sys.executable, not a shell string"
|
|
|
|
|
|
def test_legacy_ggml_wrappers_delegate_safely() -> None:
|
|
"""The legacy ggml entry points must delegate to the shared helper and not build their
|
|
own subprocess invocation."""
|
|
source = SAVE_PY.read_text(encoding = "utf-8")
|
|
for function_name in (
|
|
"unsloth_convert_lora_to_ggml_and_push_to_hub",
|
|
"unsloth_convert_lora_to_ggml_and_save_locally",
|
|
):
|
|
node = _get_function(source, function_name)
|
|
calls = [c for c in ast.walk(node) if isinstance(c, ast.Call)]
|
|
assert any(
|
|
isinstance(c.func, ast.Name) and c.func.id == "_unsloth_save_lora_gguf" for c in calls
|
|
), f"{function_name} should delegate to _unsloth_save_lora_gguf"
|
|
assert not _popen_calls(
|
|
node
|
|
), f"{function_name} should not call subprocess.Popen directly anymore"
|