From 631c05603cc147a00d3abb9bbd7353e2e0622e79 Mon Sep 17 00:00:00 2001 From: Grzegorz Date: Tue, 3 Nov 2026 06:41:21 +0100 Subject: [PATCH] fix(cookbook): make the Dependencies recipe Run button pass validation (#6725) (#6726) The recipe Run button sent repo_id " setup"; the space fails the pip package-spec check, so every Run returned "Invalid pip package name". It now sends "-setup". The vLLM/SGLang recipes start with `uv pip install ... --torch-backend auto`, but uv was not in the serve command allowlist. Allow uv only for the `pip install` subcommand. Co-authored-by: Claude Opus 5.5 --- routes/cookbook_helpers.py | 7 ++++++- static/js/cookbook.js | 6 ++++-- tests/test_cookbook_helpers.py | 34 ++++++++++++++++++++++++++++++++++ 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/routes/cookbook_helpers.py b/routes/cookbook_helpers.py index e45881687..b866202eb 100644 --- a/routes/cookbook_helpers.py +++ b/routes/cookbook_helpers.py @@ -728,8 +728,13 @@ def _check_serve_binary(seg: str) -> None: if not tokens: return env_re = re.compile(r"^[A-Za-z_][A-Za-z0-9_]*=") - first = next((t for t in tokens if not env_re.match(t)), "") + idx = next((i for i, t in enumerate(tokens) if not env_re.match(t)), len(tokens)) + first = tokens[idx] if idx < len(tokens) else "" base = os.path.basename(first) + # The vLLM/SGLang Dependencies recipes install with `uv pip install … + # --torch-backend auto`. Allow uv for that subcommand only, not `uv run`. + if base == "uv" and tokens[idx + 1:idx + 3] == ["pip", "install"]: + return if base not in _SERVE_CMD_ALLOWLIST: raise HTTPException( 400, diff --git a/static/js/cookbook.js b/static/js/cookbook.js index c3a3c817d..88f04ee16 100644 --- a/static/js/cookbook.js +++ b/static/js/cookbook.js @@ -1762,7 +1762,9 @@ async function _fetchDependencies() { envPrefix = 'eval "$(conda shell.bash hook)" && conda activate ' + _shellQuote(_envState.envPath); } const reqBody = { - repo_id: `${backend} setup`, + // The server validates repo_id as a pip package spec for pip + // installs, so it can't contain a space. + repo_id: `${backend}-setup`, cmd: cmd, remote_host: _envState.remoteHost || undefined, ssh_port: _getPort(_envState.remoteHost) || undefined, @@ -1780,7 +1782,7 @@ async function _fetchDependencies() { uiModule.showToast('Run failed: ' + String(data.detail || data.error || `HTTP ${res.status}`).slice(0, 200)); return; } - const payload = { repo_id: `${backend} setup`, _cmd: cmd, remote_host: _envState.remoteHost || '', _dep: true }; + const payload = { repo_id: reqBody.repo_id, _cmd: cmd, remote_host: _envState.remoteHost || '', _dep: true }; _addTask(data.session_id, `${backend} setup`, 'download', payload); uiModule.showToast(`Running ${backend} setup on ${targetHost}…`); } catch (err) { diff --git a/tests/test_cookbook_helpers.py b/tests/test_cookbook_helpers.py index fdac772cd..7681e3daf 100644 --- a/tests/test_cookbook_helpers.py +++ b/tests/test_cookbook_helpers.py @@ -2,6 +2,7 @@ import json import os import subprocess import sys +import re from pathlib import Path import pytest @@ -1026,3 +1027,36 @@ def test_validate_serve_cmd_rejects_unrelated_subshell_pipelines(): ]: with pytest.raises(HTTPException): _validate_serve_cmd(cmd) + + +def _dependency_recipe_pip_commands(): + src = (Path(__file__).resolve().parents[1] / "static" / "js" / "cookbook-deps-recipes.js").read_text(encoding="utf-8") + return re.findall(r"pip:\s*\{\s*commands:\s*\['((?:[^'\\]|\\.)*)'\]\s*\}", src) + + +def test_validate_serve_cmd_accepts_single_line_dependency_recipes(): + # The Dependencies recipe "Run" button posts these to /api/model/serve. + cmds = _dependency_recipe_pip_commands() + assert 'uv pip install -U vllm --torch-backend auto' in cmds + assert 'uv pip install -U "sglang[all]" --torch-backend auto' in cmds + for cmd in cmds: + assert _validate_serve_cmd(cmd) == cmd + + +@pytest.mark.parametrize("cmd", [ + "uv run python -c 'print(1)'", + "uv tool install ruff", + "uv pip uninstall vllm", + "uv", +]) +def test_validate_serve_cmd_allows_uv_only_for_pip_install(cmd): + with pytest.raises(HTTPException): + _validate_serve_cmd(cmd) + + +def test_dependency_recipe_run_sends_a_valid_pip_repo_id(): + # A pip-install repo_id must match the server's package-spec regex; the old + # " setup" value had a space and every Run got "Invalid pip package name". + src = (Path(__file__).resolve().parents[1] / "static" / "js" / "cookbook.js").read_text(encoding="utf-8") + assert "repo_id: `${backend}-setup`," in src + assert "repo_id: `${backend} setup`" not in src