mirror of
https://gitee.com/wanwujie/deer-flow
synced 2026-04-19 12:24:46 +08:00
fix(sandbox): preserve PermissionError messages and allow /mnt/user-data root in resolve_local_tool_path
Co-authored-by: WillemJiang <219644+WillemJiang@users.noreply.github.com>
This commit is contained in:
@@ -144,6 +144,11 @@ def resolve_local_tool_path(path: str, thread_data: ThreadDataState | None) -> s
|
|||||||
if not allowed_roots:
|
if not allowed_roots:
|
||||||
raise SandboxRuntimeError("No allowed local sandbox directories configured")
|
raise SandboxRuntimeError("No allowed local sandbox directories configured")
|
||||||
|
|
||||||
|
# Also allow the virtual root itself (/mnt/user-data) to map to the common parent dir.
|
||||||
|
common_parent = _thread_virtual_to_actual_mappings(thread_data).get(VIRTUAL_PATH_PREFIX)
|
||||||
|
if common_parent is not None:
|
||||||
|
allowed_roots.append(Path(common_parent).resolve())
|
||||||
|
|
||||||
for root in allowed_roots:
|
for root in allowed_roots:
|
||||||
try:
|
try:
|
||||||
resolved.relative_to(root)
|
resolved.relative_to(root)
|
||||||
@@ -405,8 +410,8 @@ def ls_tool(runtime: ToolRuntime[ContextT, ThreadState], description: str, path:
|
|||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return f"Error: Directory not found: {requested_path}"
|
return f"Error: Directory not found: {requested_path}"
|
||||||
except PermissionError:
|
except PermissionError as e:
|
||||||
return f"Error: Permission denied: {requested_path}"
|
return f"Error: {e}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error: Unexpected error listing directory: {type(e).__name__}: {e}"
|
return f"Error: Unexpected error listing directory: {type(e).__name__}: {e}"
|
||||||
|
|
||||||
@@ -444,8 +449,8 @@ def read_file_tool(
|
|||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return f"Error: File not found: {requested_path}"
|
return f"Error: File not found: {requested_path}"
|
||||||
except PermissionError:
|
except PermissionError as e:
|
||||||
return f"Error: Permission denied reading file: {requested_path}"
|
return f"Error: {e}"
|
||||||
except IsADirectoryError:
|
except IsADirectoryError:
|
||||||
return f"Error: Path is a directory, not a file: {requested_path}"
|
return f"Error: Path is a directory, not a file: {requested_path}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -478,8 +483,8 @@ def write_file_tool(
|
|||||||
return "OK"
|
return "OK"
|
||||||
except SandboxError as e:
|
except SandboxError as e:
|
||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
except PermissionError:
|
except PermissionError as e:
|
||||||
return f"Error: Permission denied writing to file: {requested_path}"
|
return f"Error: {e}"
|
||||||
except IsADirectoryError:
|
except IsADirectoryError:
|
||||||
return f"Error: Path is a directory, not a file: {requested_path}"
|
return f"Error: Path is a directory, not a file: {requested_path}"
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
@@ -529,7 +534,7 @@ def str_replace_tool(
|
|||||||
return f"Error: {e}"
|
return f"Error: {e}"
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
return f"Error: File not found: {requested_path}"
|
return f"Error: File not found: {requested_path}"
|
||||||
except PermissionError:
|
except PermissionError as e:
|
||||||
return f"Error: Permission denied accessing file: {requested_path}"
|
return f"Error: {e}"
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
return f"Error: Unexpected error replacing string: {type(e).__name__}: {e}"
|
return f"Error: Unexpected error replacing string: {type(e).__name__}: {e}"
|
||||||
|
|||||||
@@ -36,6 +36,28 @@ def test_mask_local_paths_in_output_hides_host_paths() -> None:
|
|||||||
assert "/mnt/user-data/workspace/result.txt" in masked
|
assert "/mnt/user-data/workspace/result.txt" in masked
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_local_tool_path_resolves_valid_virtual_path() -> None:
|
||||||
|
thread_data = {
|
||||||
|
"workspace_path": "/tmp/deer-flow/threads/t1/user-data/workspace",
|
||||||
|
"uploads_path": "/tmp/deer-flow/threads/t1/user-data/uploads",
|
||||||
|
"outputs_path": "/tmp/deer-flow/threads/t1/user-data/outputs",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = resolve_local_tool_path("/mnt/user-data/workspace/report.txt", thread_data)
|
||||||
|
assert result == "/tmp/deer-flow/threads/t1/user-data/workspace/report.txt"
|
||||||
|
|
||||||
|
|
||||||
|
def test_resolve_local_tool_path_allows_virtual_root() -> None:
|
||||||
|
thread_data = {
|
||||||
|
"workspace_path": "/tmp/deer-flow/threads/t1/user-data/workspace",
|
||||||
|
"uploads_path": "/tmp/deer-flow/threads/t1/user-data/uploads",
|
||||||
|
"outputs_path": "/tmp/deer-flow/threads/t1/user-data/outputs",
|
||||||
|
}
|
||||||
|
|
||||||
|
result = resolve_local_tool_path("/mnt/user-data", thread_data)
|
||||||
|
assert result == "/tmp/deer-flow/threads/t1/user-data"
|
||||||
|
|
||||||
|
|
||||||
def test_resolve_local_tool_path_rejects_non_virtual_path() -> None:
|
def test_resolve_local_tool_path_rejects_non_virtual_path() -> None:
|
||||||
thread_data = {
|
thread_data = {
|
||||||
"workspace_path": "/tmp/deer-flow/threads/t1/user-data/workspace",
|
"workspace_path": "/tmp/deer-flow/threads/t1/user-data/workspace",
|
||||||
|
|||||||
Reference in New Issue
Block a user