mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-10-26 13:16:24 +00:00
fix(paths): reject space-padded Windows device names (#6458)
This commit is contained in:
parent
c15ecb688d
commit
7d54feea07
@ -2124,6 +2124,9 @@ 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²`, the console
|
||||
aliases `CONIN$` and `CONOUT$`, and names with extensions such as `com³.txt`.
|
||||
ASCII spaces before the extension do not make a device name portable:
|
||||
`NUL .txt` and `COM1 .log` are rejected too. Ordinary names such as
|
||||
`report .txt` are preserved unchanged.
|
||||
Rename these files before creating or uploading them so the same file tree
|
||||
remains usable on Windows.
|
||||
|
||||
|
||||
@ -94,6 +94,10 @@ every platform, including the `COM`/`LPT` aliases with superscript ¹, ² and ³
|
||||
The console aliases `CONIN$` and `CONOUT$` are reserved too; match them
|
||||
case-insensitively before the first dot, without rejecting longer ordinary
|
||||
names such as `CONIN$notes.txt`.
|
||||
Device-name comparison ignores ASCII spaces before the first dot, so
|
||||
`NUL .txt` and `COM1 .log` remain reserved. Trim the comparison stem only,
|
||||
not the supplied filename; ordinary names such as `report .txt` and
|
||||
non-ASCII whitespace remain unchanged.
|
||||
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.
|
||||
|
||||
@ -52,7 +52,9 @@ def windows_incompatible_segment(segment: str) -> str | None:
|
||||
return None
|
||||
if segment.endswith((" ", ".")):
|
||||
return "trailing dot or space"
|
||||
stem = segment.split(".", 1)[0]
|
||||
# Windows ignores ASCII spaces before a device name's extension (NUL .txt).
|
||||
# Trim only for comparison; ordinary filenames must retain their spaces.
|
||||
stem = segment.split(".", 1)[0].rstrip(" ")
|
||||
if stem.upper() in _WINDOWS_RESERVED_NAMES:
|
||||
return "reserved Windows device name"
|
||||
return None
|
||||
|
||||
@ -519,6 +519,14 @@ def test_validate_local_tool_path_rejects_new_console_device_names(name: str) ->
|
||||
validate_local_tool_path(path, _THREAD_DATA, read_only=True)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("name", ["NUL .txt", "con .log", "COM1 .txt", "lpt² .md", "CONIN$ .txt", "conout$ .log"])
|
||||
def test_validate_local_tool_path_rejects_new_padded_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"):
|
||||
|
||||
@ -86,6 +86,12 @@ def test_ensure_safe_support_path_rejects_console_device_names(storage, skill_di
|
||||
storage.ensure_safe_support_path("demo-skill", path)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("path", ["assets/NUL .txt", "scripts/con .log", "assets/COM1 .txt", "assets/lpt² .md", "assets/CONIN$ .txt", "assets/conout$ .log", "assets/COM1 .txt/icon.png"])
|
||||
def test_ensure_safe_support_path_rejects_padded_device_names(storage, skill_dir, path):
|
||||
with pytest.raises(ValueError, match="reserved Windows device name"):
|
||||
storage.ensure_safe_support_path("demo-skill", path)
|
||||
|
||||
|
||||
def test_read_text_or_none_decodes_text_and_returns_none_for_binary(tmp_path: Path):
|
||||
text = tmp_path / "t.md"
|
||||
text.write_text("héllo", encoding="utf-8")
|
||||
|
||||
@ -108,6 +108,15 @@ class TestNormalizeFilename:
|
||||
with pytest.raises(ValueError, match="reserved Windows device name"):
|
||||
normalize_filename(filename)
|
||||
|
||||
@pytest.mark.parametrize("filename", ["NUL .txt", "con .log", "COM1 .txt", "lpt² .md", "CONIN$ .txt", "conout$ .log"])
|
||||
def test_rejects_windows_device_names_with_spaces_before_extension(self, filename):
|
||||
with pytest.raises(ValueError, match="reserved Windows device name"):
|
||||
normalize_filename(filename)
|
||||
|
||||
@pytest.mark.parametrize("filename", ["report .txt", "NULnotes .txt", "COM¹notes .txt", "NUL\u00a0.txt"])
|
||||
def test_preserves_spaces_in_ordinary_filename_stems(self, filename):
|
||||
assert normalize_filename(filename) == filename
|
||||
|
||||
@pytest.mark.parametrize("filename", ["CONIN", "CONOUT", "CONIN$notes.txt", "CONOUT$notes.log"])
|
||||
def test_allows_names_resembling_console_devices(self, filename):
|
||||
assert normalize_filename(filename) == filename
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user