v0.23.0:0 - local / fine-tuned model support
Add models that live as a directory on a Spark (e.g. LoRA-merged fine-tunes), not just Hugging Face repos. - ModelDef gains local_path; a model must set exactly one of repo / local_path. The validator also enforces the local-path whitelist and that any --chat-template lives inside local_path (only that dir is mounted). - build_launch_command bind-mounts the dir into the vLLM container at the SAME host==container path via the launch script's VLLM_SPARK_EXTRA_DOCKER_ARGS hook, then `vllm serve <dir>`. No launch-cluster.sh change (verified the upstream expands that var unquoted; contract noted in runbook.md). - shellsafe.validate_local_path: absolute path, charset whitelist, no '.'/'..'. - POST /api/models validates the full entry via ModelDef before persisting, so a bad entry can't be written and then break catalog load; _merge_overrides skips an invalid override entry instead of failing the whole catalog. - disk.py size-probes a local path with du; disk-delete refused for local models. - UI: "+ Add local model" dialog, `local` badge, path shown instead of an HF link, delete button hidden for local models. - Tests: local launch + injection round-trip, chat-template location, traversal, exactly-one-source, _merge_overrides skip-invalid (94 pass). Reviewer-agent pass; findings addressed.
This commit is contained in:
@@ -6,7 +6,12 @@ use `validate_x(v)` inline.
|
||||
"""
|
||||
import pytest
|
||||
|
||||
from app.shellsafe import validate_container, validate_image, validate_repo
|
||||
from app.shellsafe import (
|
||||
validate_container,
|
||||
validate_image,
|
||||
validate_local_path,
|
||||
validate_repo,
|
||||
)
|
||||
|
||||
# Shell metacharacters that must never survive any validator — these are the
|
||||
# actual injection vectors. (Path traversal like "../" is NOT in scope here:
|
||||
@@ -96,3 +101,27 @@ def test_container_valid_passes_through_unchanged(name):
|
||||
def test_container_rejects_malformed_and_hostile(name):
|
||||
with pytest.raises(ValueError):
|
||||
validate_container(name)
|
||||
|
||||
|
||||
# ---- validate_local_path: absolute model dir, no traversal/metacharacters ----
|
||||
|
||||
@pytest.mark.parametrize("path", [
|
||||
"/home/modelo/models/gemma-4-31B-ten31-v2",
|
||||
"/data/models/ft.v2_1",
|
||||
"/srv/m/a-b/c",
|
||||
])
|
||||
def test_local_path_valid_passes_through_unchanged(path):
|
||||
assert validate_local_path(path) == path
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", [
|
||||
"",
|
||||
"relative/path", # must be absolute
|
||||
"~/models/x", # no ~ expansion
|
||||
"/models/../etc/shadow", # '..' traversal
|
||||
"/models/./x", # '.' segment
|
||||
"/a" * 300, # over the 512 cap (600 chars)
|
||||
] + [f"/models/x{h}" for h in HOSTILE])
|
||||
def test_local_path_rejects_relative_traversal_and_hostile(path):
|
||||
with pytest.raises(ValueError):
|
||||
validate_local_path(path)
|
||||
|
||||
Reference in New Issue
Block a user