mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(paths): reject superscript Windows device names (#6148)
This commit is contained in:
parent
c968f9536f
commit
3a7e160a55
@ -1902,6 +1902,11 @@ also fails, for example because of a Windows sharing violation.
|
||||
Once the file is published, a temporary-file cleanup failure is logged without
|
||||
failing the upload; hidden staging files are left for the startup sweep.
|
||||
|
||||
Uploads, new skill support files, and new local sandbox paths reject Windows
|
||||
reserved device names on every platform, including `COM¹`, `LPT²` and names with
|
||||
extensions such as `com³.txt`. Rename these files before creating or uploading
|
||||
them so the same file tree remains usable on Windows.
|
||||
|
||||
Uploaded filenames matching `.upload-*.part` are rejected because that pattern is
|
||||
reserved for temporary staging files. Rename such a file before uploading it.
|
||||
The restriction includes Windows aliases with trailing dots or spaces and
|
||||
|
||||
@ -31,3 +31,11 @@ characters, and the original file bytes.
|
||||
boundary for artifacts and project documents. Keep platform aliases such as
|
||||
Windows' `image/svg` aligned with their standard active type (`image/svg+xml`),
|
||||
and preserve the generic `+xml` rule.
|
||||
|
||||
### Host Path Portability
|
||||
|
||||
`host_paths.py` rejects Windows device names for host-visible creation paths on
|
||||
every platform, including the `COM`/`LPT` aliases with superscript ¹, ² and ³.
|
||||
Do not normalize arbitrary Unicode digits into device numbers: names such as
|
||||
`COM⁴.txt` and `COM¹notes.txt` are ordinary portable names. Read/removal callers
|
||||
retain their existing portability exemptions.
|
||||
|
||||
@ -2,7 +2,9 @@
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
_WINDOWS_RESERVED_NAMES = frozenset({"CON", "PRN", "AUX", "NUL"} | {f"COM{i}" for i in range(1, 10)} | {f"LPT{i}" for i in range(1, 10)})
|
||||
# Windows also treats the ISO-8859-1 superscript digits ¹, ² and ³ as device numbers.
|
||||
# https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
|
||||
_WINDOWS_RESERVED_NAMES = frozenset({"CON", "PRN", "AUX", "NUL"} | {f"COM{i}" for i in "123456789¹²³"} | {f"LPT{i}" for i in "123456789¹²³"})
|
||||
|
||||
|
||||
def windows_incompatible_segment(segment: str) -> str | None:
|
||||
|
||||
@ -503,6 +503,14 @@ def test_validate_local_tool_path_write_rejects_new_nonportable_names() -> None:
|
||||
validate_local_tool_path(f"{VIRTUAL_PATH_PREFIX}/uploads/../../etc/passwd", _THREAD_DATA, read_only=False)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["COM¹", "com².txt", "COM³.log", "LPT¹", "lpt².md", "LPT³.pdf"])
|
||||
def test_validate_local_tool_path_rejects_new_superscript_device_names(name: str) -> None:
|
||||
path = f"{VIRTUAL_PATH_PREFIX}/workspace/{name}"
|
||||
with pytest.raises(PermissionError, match="Access denied"):
|
||||
validate_local_tool_path(path, _THREAD_DATA, read_only=False)
|
||||
validate_local_tool_path(path, _THREAD_DATA, read_only=True)
|
||||
|
||||
|
||||
def test_validate_local_tool_path_rejects_traversal_in_user_data() -> None:
|
||||
"""Path traversal via .. in user-data paths must be rejected."""
|
||||
with pytest.raises(PermissionError, match="path traversal"):
|
||||
|
||||
@ -74,7 +74,7 @@ def test_ensure_safe_support_path_requires_a_filename_below_the_subdir(storage,
|
||||
assert storage.ensure_safe_support_path("demo-skill", "assets/logo.png") == (skill_dir / "assets" / "logo.png").resolve()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["assets/CON", "assets/nul.txt", "assets/logo.png.", "assets/notes.md "])
|
||||
@pytest.mark.parametrize("path", ["assets/CON", "assets/nul.txt", "assets/com¹.txt", "assets/LPT²", "assets/LPT³/icon.png", "assets/logo.png.", "assets/notes.md "])
|
||||
def test_ensure_safe_support_path_rejects_windows_incompatible_names(storage, skill_dir, path):
|
||||
with pytest.raises(ValueError, match="not portable to Windows"):
|
||||
storage.ensure_safe_support_path("demo-skill", path)
|
||||
|
||||
@ -94,13 +94,13 @@ class TestNormalizeFilename:
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filename",
|
||||
["CON", "con.txt", "PRN", "AUX", "NUL", "COM1", "COM9", "LPT1", "LPT9", "file.txt.", "file.txt ", "a.", "folder/CON"],
|
||||
["CON", "con.txt", "PRN", "AUX", "NUL", "COM1", "COM9", "LPT1", "LPT9", "COM¹", "com².txt", "COM³.log", "LPT¹", "lpt².md", "LPT³.pdf", "file.txt.", "file.txt ", "a.", "folder/CON"],
|
||||
)
|
||||
def test_rejects_windows_incompatible_names(self, filename):
|
||||
with pytest.raises(ValueError, match="not portable to Windows"):
|
||||
normalize_filename(filename)
|
||||
|
||||
@pytest.mark.parametrize("filename", ["report.pdf", "contour.txt", "console.log", "COM0", "COM10", ".gitignore"])
|
||||
@pytest.mark.parametrize("filename", ["report.pdf", "contour.txt", "console.log", "COM0", "COM10", "COM⁴.txt", "LPT⁵", "COM¹notes.txt", "LPT²0.md", ".gitignore"])
|
||||
def test_allows_portable_names(self, filename):
|
||||
assert normalize_filename(filename) == filename
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user