From 7229d86bb31d19d51937548fdce91fa74820c986 Mon Sep 17 00:00:00 2001 From: fengxiaohu <975326527@qq.com> Date: Fri, 27 Feb 2026 21:46:46 +0800 Subject: [PATCH] fix(shell): parse full Windows absolute paths in workspace guard --- nanobot/agent/tools/shell.py | 18 +++++++++++------- tests/test_tool_validation.py | 20 ++++++++++++++++++++ 2 files changed, 31 insertions(+), 7 deletions(-) diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index e3592a7..796d1fb 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -134,13 +134,7 @@ class ExecTool(Tool): cwd_path = Path(cwd).resolve() - win_paths = re.findall(r"[A-Za-z]:\\[^\\\"']+", cmd) - # Only match absolute paths — avoid false positives on relative - # paths like ".venv/bin/python" where "/bin/python" would be - # incorrectly extracted by the old pattern. - posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", cmd) - - for raw in win_paths + posix_paths: + for raw in self._extract_absolute_paths(cmd): try: p = Path(raw.strip()).resolve() except Exception: @@ -149,3 +143,13 @@ class ExecTool(Tool): return "Error: Command blocked by safety guard (path outside working dir)" return None + + @staticmethod + def _extract_absolute_paths(command: str) -> list[str]: + # Match Windows absolute paths without truncating at backslashes. + win_paths = re.findall(r"[A-Za-z]:\\[^\s\"'|><;]+", command) + # Only match absolute paths — avoid false positives on relative + # paths like ".venv/bin/python" where "/bin/python" would be + # incorrectly extracted by the old pattern. + posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", command) + return win_paths + posix_paths diff --git a/tests/test_tool_validation.py b/tests/test_tool_validation.py index f11c667..cb50fb0 100644 --- a/tests/test_tool_validation.py +++ b/tests/test_tool_validation.py @@ -2,6 +2,7 @@ from typing import Any from nanobot.agent.tools.base import Tool from nanobot.agent.tools.registry import ToolRegistry +from nanobot.agent.tools.shell import ExecTool class SampleTool(Tool): @@ -86,3 +87,22 @@ async def test_registry_returns_validation_error() -> None: reg.register(SampleTool()) result = await reg.execute("sample", {"query": "hi"}) assert "Invalid parameters" in result + + +def test_exec_extract_absolute_paths_keeps_full_windows_path() -> None: + cmd = r"type C:\user\workspace\txt" + paths = ExecTool._extract_absolute_paths(cmd) + assert paths == [r"C:\user\workspace\txt"] + + +def test_exec_extract_absolute_paths_ignores_relative_posix_segments() -> None: + cmd = ".venv/bin/python script.py" + paths = ExecTool._extract_absolute_paths(cmd) + assert "/bin/python" not in paths + + +def test_exec_extract_absolute_paths_captures_posix_absolute_paths() -> None: + cmd = "cat /tmp/data.txt > /tmp/out.txt" + paths = ExecTool._extract_absolute_paths(cmd) + assert "/tmp/data.txt" in paths + assert "/tmp/out.txt" in paths