fix(shell): parse full Windows absolute paths in workspace guard
This commit is contained in:
@@ -134,13 +134,7 @@ class ExecTool(Tool):
|
|||||||
|
|
||||||
cwd_path = Path(cwd).resolve()
|
cwd_path = Path(cwd).resolve()
|
||||||
|
|
||||||
win_paths = re.findall(r"[A-Za-z]:\\[^\\\"']+", cmd)
|
for raw in self._extract_absolute_paths(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:
|
|
||||||
try:
|
try:
|
||||||
p = Path(raw.strip()).resolve()
|
p = Path(raw.strip()).resolve()
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -149,3 +143,13 @@ class ExecTool(Tool):
|
|||||||
return "Error: Command blocked by safety guard (path outside working dir)"
|
return "Error: Command blocked by safety guard (path outside working dir)"
|
||||||
|
|
||||||
return None
|
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
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from typing import Any
|
|||||||
|
|
||||||
from nanobot.agent.tools.base import Tool
|
from nanobot.agent.tools.base import Tool
|
||||||
from nanobot.agent.tools.registry import ToolRegistry
|
from nanobot.agent.tools.registry import ToolRegistry
|
||||||
|
from nanobot.agent.tools.shell import ExecTool
|
||||||
|
|
||||||
|
|
||||||
class SampleTool(Tool):
|
class SampleTool(Tool):
|
||||||
@@ -86,3 +87,22 @@ async def test_registry_returns_validation_error() -> None:
|
|||||||
reg.register(SampleTool())
|
reg.register(SampleTool())
|
||||||
result = await reg.execute("sample", {"query": "hi"})
|
result = await reg.execute("sample", {"query": "hi"})
|
||||||
assert "Invalid parameters" in result
|
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
|
||||||
|
|||||||
Reference in New Issue
Block a user