fix(paths): accept persisted separators on every host

This commit is contained in:
debpalash
2026-08-20 06:08:28 +05:30
parent ee7202b1eb
commit 3b64d317ae
3 changed files with 10 additions and 10 deletions
+1
View File
@@ -29,6 +29,7 @@ the frozen-backend fallback mirror it for their toolchains.
- The OmniVoice guide now covers combining style attributes with a reference clip (consistent instruct stabilizes cloning; the reference wins conflicts), inline pronunciation control (pinyin / CMU phonemes), and corrects the claim that the default engine can't do voice design — it can, from attributes (#1565)
### Fixed
- Stored artifact subpaths now resolve after moving a data directory between Windows, macOS, Linux, and Docker, while traversal and symlink escapes remain blocked (#1559) — thanks @Eman-Yousaf!
- A remote browser hitting an API-key-configured server's admin 403 now gets the API-key login form instead of endless console 403s, while desktop and PIN-only/no-key servers keep the plain loopback error so guests are never offered a login no key can satisfy (#1568) — thanks @paoloantinori!
- The crash-isolated ASR sidecar and its download preflight now agree on which model to load — setting the shared faster-whisper model variable applies to both variants instead of the sidecar quietly using a different one (#1556)
- "Ready" now requires the deep health probe (a working database-backed route), not just the identity probe — a backend whose install broke underneath can no longer be announced up while every real request fails (#1548)
+4 -5
View File
@@ -59,11 +59,10 @@ def resolve_within(root: os.PathLike[str] | str, value: os.PathLike[str] | str)
raw = os.fspath(value) if value is not None else ""
if not isinstance(raw, str) or not raw:
raise UnsafePath("path is empty")
# Treat both separator families as structural on every host. Otherwise a
# Windows traversal string is an innocent-looking filename when validated
# on Linux (and can become dangerous after persisted data is moved).
if os.sep != "\\" and ("\\" in raw or bool(ntpath.splitdrive(raw)[0])):
raise UnsafePath("path uses a foreign separator or drive")
# Treat both separator families as structural on every host while still
# rejecting Windows drive paths before rebuilding relative components.
if os.sep != "\\" and bool(ntpath.splitdrive(raw)[0]):
raise UnsafePath("path uses a drive")
root_path = Path(root).expanduser().resolve(strict=False)
root_text = str(root_path)
if os.path.isabs(raw):
+5 -5
View File
@@ -65,17 +65,17 @@ def test_stored_subpaths_split_on_both_separator_families():
assert _PATH_SEPARATORS.split(r"sub\voice.wav") == ["sub", "voice.wav"]
def test_resolve_within_reads_a_stored_subpath(tmp_path):
@pytest.mark.parametrize("stored_path", ["sub/voice.wav", r"sub\voice.wav"])
def test_resolve_within_reads_a_stored_subpath(tmp_path, stored_path):
"""A persisted sub-path resolves to the same file on Windows and POSIX.
Windows accepts ``/`` as a real separator, so a row written by a Linux
host (or a Docker deployment) must resolve there exactly as it does on
POSIX instead of being rejected as one unsafe component.
Rows written on Windows, POSIX, or Docker must resolve identically after
the same data directory is opened on another supported host.
"""
from core.path_security import resolve_within
root = tmp_path / "root"
(root / "sub").mkdir(parents=True)
assert resolve_within(root, "sub/voice.wav") == root / "sub" / "voice.wav"
assert resolve_within(root, stored_path) == root / "sub" / "voice.wav"
def test_resolve_within_rejects_traversal_through_either_separator(tmp_path):