mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-07-09 16:08:31 +00:00
fix(sandbox): normalize Windows backslash paths to forward slashes in bash commands (#3869)
Some checks are pending
Backend Blocking IO / backend-blocking-io (push) Waiting to run
Unit Tests / backend-unit-tests (push) Waiting to run
E2E Tests / e2e-tests (push) Waiting to run
Frontend Unit Tests / frontend-unit-tests (push) Waiting to run
Lint Check / lint-backend (push) Waiting to run
Lint Check / lint-frontend (push) Waiting to run
Replay E2E (front-back contract) / Layer 1 — backend golden (no API key) (push) Waiting to run
Replay E2E (front-back contract) / Layer 2 — full-stack render (no API key) (push) Waiting to run
Some checks are pending
Backend Blocking IO / backend-blocking-io (push) Waiting to run
Unit Tests / backend-unit-tests (push) Waiting to run
E2E Tests / e2e-tests (push) Waiting to run
Frontend Unit Tests / frontend-unit-tests (push) Waiting to run
Lint Check / lint-backend (push) Waiting to run
Lint Check / lint-frontend (push) Waiting to run
Replay E2E (front-back contract) / Layer 1 — backend golden (no API key) (push) Waiting to run
Replay E2E (front-back contract) / Layer 2 — full-stack render (no API key) (push) Waiting to run
* fix(sandbox): normalize Windows backslash paths to forward slashes in bash commands
On Windows, `replace_virtual_paths_in_command()` and
`LocalSandbox._resolve_paths_in_command()` resolve virtual paths
(/mnt/skills, /mnt/user-data, /mnt/acp-workspace, custom mounts) to
host paths with backslashes (C:\Users\admin\...). Bash interprets
\U, \a, \d, \s, \n, \t as escape sequences, mangling the path.
Fix: add `.replace("\\", "/")` to all path resolution callbacks so
resolved paths use forward slashes, which bash handles correctly on
all platforms and Windows APIs/Python's open() accept natively.
Fixes #3865
* test(sandbox): regression tests for Windows backslash path normalization
Covers replace_virtual_paths_in_command and LocalSandbox._resolve_paths_in_command
with Windows-style backslash paths, asserting no backslashes survive in output.
Closes #3865
* test(sandbox): fix ACP test failure, keep 6 passing regression tests
Removed test_acp_workspace_no_backslash which triggered path traversal
validation. Remaining 6 tests cover user-data (3), skills (1), and
LocalSandbox custom-mount (2) paths — all with Windows backslash paths.
* test(sandbox): restore ACP Windows path normalization coverage
This commit is contained in:
parent
c05dd46e25
commit
cd982d6751
3 changed files with 85 additions and 4 deletions
|
|
@ -376,7 +376,9 @@ class LocalSandbox(Sandbox):
|
|||
|
||||
def replace_match(match: re.Match) -> str:
|
||||
matched_path = match.group(0)
|
||||
return self._resolve_path(matched_path)
|
||||
# Normalize to forward slashes so bash doesn't interpret Windows
|
||||
# backslash sequences (\\U, \\a, \\d, \\s, \\n, \\t) as escapes.
|
||||
return self._resolve_path(matched_path).replace("\\", "/")
|
||||
|
||||
return pattern.sub(replace_match, command)
|
||||
|
||||
|
|
|
|||
|
|
@ -1049,7 +1049,7 @@ def replace_virtual_paths_in_command(command: str, thread_data: ThreadDataState
|
|||
skills_pattern = re.compile(rf"{re.escape(skills_container)}(/[^\s\"';&|<>()]*)?")
|
||||
|
||||
def replace_skills_match(match: re.Match) -> str:
|
||||
return _resolve_skills_path(match.group(0))
|
||||
return _resolve_skills_path(match.group(0)).replace("\\", "/")
|
||||
|
||||
result = skills_pattern.sub(replace_skills_match, result)
|
||||
|
||||
|
|
@ -1060,7 +1060,7 @@ def replace_virtual_paths_in_command(command: str, thread_data: ThreadDataState
|
|||
acp_pattern = re.compile(rf"{re.escape(_ACP_WORKSPACE_VIRTUAL_PATH)}(/[^\s\"';&|<>()]*)?")
|
||||
|
||||
def replace_acp_match(match: re.Match, _tid: str | None = _thread_id) -> str:
|
||||
return _resolve_acp_workspace_path(match.group(0), _tid)
|
||||
return _resolve_acp_workspace_path(match.group(0), _tid).replace("\\", "/")
|
||||
|
||||
result = acp_pattern.sub(replace_acp_match, result)
|
||||
|
||||
|
|
@ -1071,7 +1071,7 @@ def replace_virtual_paths_in_command(command: str, thread_data: ThreadDataState
|
|||
pattern = re.compile(rf"{re.escape(VIRTUAL_PATH_PREFIX)}(/[^\s\"';&|<>()]*)?")
|
||||
|
||||
def replace_user_data_match(match: re.Match) -> str:
|
||||
return replace_virtual_path(match.group(0), thread_data)
|
||||
return replace_virtual_path(match.group(0), thread_data).replace("\\", "/")
|
||||
|
||||
result = pattern.sub(replace_user_data_match, result)
|
||||
|
||||
|
|
|
|||
79
backend/tests/test_sandbox_windows_path_normalization.py
Normal file
79
backend/tests/test_sandbox_windows_path_normalization.py
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
"""Regression tests for Windows backslash path normalization.
|
||||
|
||||
Ensures that replace_virtual_paths_in_command and LocalSandbox._resolve_paths_in_command
|
||||
return forward-slash paths when the host paths use backslashes (Windows).
|
||||
"""
|
||||
|
||||
from unittest.mock import patch
|
||||
|
||||
from deerflow.sandbox.local.local_sandbox import LocalSandbox, PathMapping
|
||||
from deerflow.sandbox.tools import replace_virtual_paths_in_command
|
||||
|
||||
# Windows-style thread data with backslash paths
|
||||
_WIN_THREAD_DATA = {
|
||||
"workspace_path": r"C:\Users\admin\deer-flow\backend\.deer-flow\users\user1\threads\t1\user-data\workspace",
|
||||
"uploads_path": r"C:\Users\admin\deer-flow\backend\.deer-flow\users\user1\threads\t1\user-data\uploads",
|
||||
"outputs_path": r"C:\Users\admin\deer-flow\backend\.deer-flow\users\user1\threads\t1\user-data\outputs",
|
||||
}
|
||||
|
||||
|
||||
class TestReplaceVirtualPathsWindows:
|
||||
"""replace_virtual_paths_in_command must normalize backslashes to forward slashes."""
|
||||
|
||||
def test_user_data_workspace_no_backslash(self) -> None:
|
||||
cmd = "cat /mnt/user-data/workspace/data.json"
|
||||
result = replace_virtual_paths_in_command(cmd, _WIN_THREAD_DATA)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
|
||||
def test_user_data_outputs_no_backslash(self) -> None:
|
||||
cmd = "ls /mnt/user-data/outputs/report.html"
|
||||
result = replace_virtual_paths_in_command(cmd, _WIN_THREAD_DATA)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
|
||||
def test_user_data_subdir_no_backslash(self) -> None:
|
||||
cmd = "cat /mnt/user-data/workspace/subdir/file.txt"
|
||||
result = replace_virtual_paths_in_command(cmd, _WIN_THREAD_DATA)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
|
||||
@patch("deerflow.sandbox.tools._get_skills_host_path", return_value=r"C:\Users\admin\deer-flow\skills")
|
||||
@patch("deerflow.sandbox.tools._get_skills_container_path", return_value="/mnt/skills")
|
||||
def test_skills_path_no_backslash(self, _mock_container, _mock_host) -> None:
|
||||
cmd = "python /mnt/skills/custom/skill/scripts/run.py"
|
||||
result = replace_virtual_paths_in_command(cmd, _WIN_THREAD_DATA)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
|
||||
@patch("deerflow.sandbox.tools._resolve_acp_workspace_path", return_value=r"C:\Users\admin\deer-flow\acp-workspace\data.json")
|
||||
@patch("deerflow.sandbox.tools._get_acp_workspace_host_path", return_value=r"C:\Users\admin\deer-flow\acp-workspace")
|
||||
def test_acp_workspace_no_backslash(self, _mock_acp_host, _mock_resolve_acp) -> None:
|
||||
cmd = "cat /mnt/acp-workspace/data.json"
|
||||
result = replace_virtual_paths_in_command(cmd, _WIN_THREAD_DATA)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
assert "C:/Users/admin/deer-flow/acp-workspace/data.json" in result
|
||||
|
||||
|
||||
class TestLocalSandboxResolvePathsInCommandWindows:
|
||||
"""LocalSandbox._resolve_paths_in_command must normalize backslashes."""
|
||||
|
||||
def test_custom_mount_no_backslash(self) -> None:
|
||||
sandbox = LocalSandbox(
|
||||
"test",
|
||||
path_mappings=[
|
||||
PathMapping(container_path="/mnt/models", local_path=r"C:\Users\admin\models", read_only=True),
|
||||
],
|
||||
)
|
||||
cmd = "cat /mnt/models/weights.bin"
|
||||
result = sandbox._resolve_paths_in_command(cmd)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
assert "C:/Users/admin/models/weights.bin" in result
|
||||
|
||||
def test_user_data_no_backslash(self) -> None:
|
||||
sandbox = LocalSandbox(
|
||||
"test",
|
||||
path_mappings=[
|
||||
PathMapping(container_path="/mnt/user-data", local_path=r"C:\Users\admin\data"),
|
||||
],
|
||||
)
|
||||
cmd = "ls /mnt/user-data/workspace/file.txt"
|
||||
result = sandbox._resolve_paths_in_command(cmd)
|
||||
assert "\\" not in result, f"Backslash in: {result}"
|
||||
assert "C:/Users/admin/data/workspace/file.txt" in result
|
||||
Loading…
Add table
Add a link
Reference in a new issue