mirror of
https://github.com/pewdiepie-archdaemon/odysseus.git
synced 2026-06-16 17:55:26 -04:00
fix(cookbook): allow spaces and non-ASCII characters in model directory paths (#3473)
* fix(cookbook): allow spaces in model directory paths Allow POSIX external-drive paths and Windows drive paths with spaces while keeping shell metacharacters rejected. * fix(cookbook): also allow non-ASCII (Unicode) characters in model dir paths The ASCII-only allowlist that rejected spaces also rejected Cyrillic, accented Latin and CJK folder names (e.g. /Volumes/Модели, D:\AI Models\Модели) with 400 Invalid local_dir. Switch the path character class from [A-Za-z0-9._ -] to [\w. -] (\w is Unicode-aware on Python 3 str patterns) so localized folder names validate, while shell metacharacters (; & | ` $ quotes newlines) stay rejected. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(cookbook): reject local_dir path segments starting with '-' The local_dir allowlist includes '-', so a directory like /models/-rf (or D:\models\-rf) could be parsed as a CLI flag by hf/etc. (option injection) — and quoting does not stop a value from being read as an option. Guard against it inside the validator so the safety stays fully self-contained there rather than depending on consumers' quoting. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -22,10 +22,12 @@ from routes.cookbook_helpers import (
|
||||
_user_shell_path_bootstrap,
|
||||
_venv_safe_local_pip_install_cmd,
|
||||
_validate_gpus,
|
||||
_validate_local_dir,
|
||||
_validate_repo_id,
|
||||
_validate_serve_cmd,
|
||||
_validate_serve_model_id,
|
||||
_validate_ssh_port,
|
||||
_shell_path,
|
||||
run_ssh_command_async,
|
||||
)
|
||||
|
||||
@@ -110,6 +112,89 @@ def test_validate_ssh_port_rejects_shell_payload():
|
||||
assert _validate_ssh_port("2222") == "2222"
|
||||
|
||||
|
||||
def test_validate_local_dir_accepts_external_drive_paths_with_spaces():
|
||||
path = "/Volumes/T7 2TB/AI Models/llamacpp"
|
||||
|
||||
assert _validate_local_dir(path) == path
|
||||
assert _validate_local_dir(f'"{path}"') == path
|
||||
assert _shell_path(f"{path}/Qwen3-8B") == '"/Volumes/T7 2TB/AI Models/llamacpp/Qwen3-8B"'
|
||||
|
||||
|
||||
def test_validate_local_dir_accepts_windows_drive_paths_with_spaces():
|
||||
backslash_path = r"D:\AI Models\llamacpp"
|
||||
slash_path = "D:/AI Models/llamacpp"
|
||||
|
||||
assert _validate_local_dir(backslash_path) == backslash_path
|
||||
assert _validate_local_dir(f"'{backslash_path}'") == backslash_path
|
||||
assert _validate_local_dir(slash_path) == slash_path
|
||||
assert _shell_path(backslash_path + r"\Qwen3-8B") == '"D:\\AI Models\\llamacpp\\Qwen3-8B"'
|
||||
|
||||
|
||||
def test_validate_local_dir_still_rejects_shell_metacharacters():
|
||||
for path in [
|
||||
"/Volumes/T7 2TB/AI Models; touch /tmp/pwned",
|
||||
"/Volumes/T7 2TB/AI Models/$(touch pwned)",
|
||||
"/Volumes/T7 2TB/AI Models/`touch pwned`",
|
||||
"/Volumes/T7 2TB/AI Models/model\nnext",
|
||||
]:
|
||||
with pytest.raises(HTTPException):
|
||||
_validate_local_dir(path)
|
||||
|
||||
|
||||
def test_validate_local_dir_rejects_windows_shell_metacharacters():
|
||||
for path in [
|
||||
r"D:\AI Models\llamacpp; touch C:\pwned",
|
||||
r"D:\AI Models\llamacpp\$(touch pwned)",
|
||||
r"D:\AI Models\llamacpp\`touch pwned`",
|
||||
"D:\\AI Models\\llamacpp\nnext",
|
||||
]:
|
||||
with pytest.raises(HTTPException):
|
||||
_validate_local_dir(path)
|
||||
|
||||
|
||||
def test_validate_local_dir_accepts_non_ascii_unicode_paths():
|
||||
# Folder names are routinely non-ASCII on localized systems; the validator
|
||||
# must accept them the same way it accepts spaces (see issue: spaces AND
|
||||
# non-ASCII chars were both rejected by the old ASCII-only allowlist).
|
||||
for path in [
|
||||
"/Volumes/Модели/llamacpp", # Cyrillic (POSIX / external drive)
|
||||
"/home/josé/models", # accented Latin
|
||||
"/Volumes/モデル/llm", # CJK
|
||||
r"D:\AI Models\Модели", # Cyrillic (Windows drive path)
|
||||
]:
|
||||
assert _validate_local_dir(path) == path
|
||||
|
||||
|
||||
def test_validate_local_dir_rejects_metacharacters_in_unicode_paths():
|
||||
# Widening the allowlist to Unicode must not reopen the injection surface:
|
||||
# shell metacharacters stay rejected even alongside non-ASCII segments.
|
||||
for path in [
|
||||
"/Volumes/Модели; touch /tmp/pwned",
|
||||
"/Volumes/Модели/$(touch pwned)",
|
||||
"/Volumes/Модели/`touch pwned`",
|
||||
"/Volumes/Модели/a|b",
|
||||
"/Volumes/Модели\nnext",
|
||||
r"D:\Модели\llamacpp & calc.exe",
|
||||
]:
|
||||
with pytest.raises(HTTPException):
|
||||
_validate_local_dir(path)
|
||||
|
||||
|
||||
def test_validate_local_dir_rejects_leading_dash_segments():
|
||||
# A path segment starting with '-' could be parsed as a CLI option by hf/etc.
|
||||
# (option injection) even when quoted, since quoting doesn't stop a value from
|
||||
# being read as a flag. The validator must reject it on every platform.
|
||||
for path in [
|
||||
"/models/-rf",
|
||||
"/models/-rf/llamacpp",
|
||||
"/-oStrictHostKeyChecking=no",
|
||||
r"D:\models\-rf",
|
||||
"D:/models/-rf",
|
||||
]:
|
||||
with pytest.raises(HTTPException):
|
||||
_validate_local_dir(path)
|
||||
|
||||
|
||||
def test_validate_gpus_accepts_indexes_only():
|
||||
assert _validate_gpus("0,1,2") == "0,1,2"
|
||||
with pytest.raises(HTTPException):
|
||||
|
||||
Reference in New Issue
Block a user