From 6c70154feeeff638cfb79a6e19d263f36ea7f5f6 Mon Sep 17 00:00:00 2001 From: suger-m Date: Tue, 10 Mar 2026 15:55:04 +0800 Subject: [PATCH 1/2] fix(exec): enforce workspace guard for home-expanded paths --- nanobot/agent/tools/shell.py | 6 ++++-- tests/test_tool_validation.py | 13 +++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index ce19920..4726e3c 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -143,7 +143,8 @@ class ExecTool(Tool): for raw in self._extract_absolute_paths(cmd): try: - p = Path(raw.strip()).resolve() + expanded = os.path.expandvars(raw.strip()) + p = Path(expanded).expanduser().resolve() except Exception: continue if p.is_absolute() and cwd_path not in p.parents and p != cwd_path: @@ -155,4 +156,5 @@ class ExecTool(Tool): def _extract_absolute_paths(command: str) -> list[str]: win_paths = re.findall(r"[A-Za-z]:\\[^\s\"'|><;]+", command) # Windows: C:\... posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", command) # POSIX: /absolute only - return win_paths + posix_paths + home_paths = re.findall(r"(?:^|[\s|>])(~[^\s\"'>;|<]*)", command) # POSIX/Windows home shortcut: ~ + return win_paths + posix_paths + home_paths diff --git a/tests/test_tool_validation.py b/tests/test_tool_validation.py index c2b4b6a..cf648bf 100644 --- a/tests/test_tool_validation.py +++ b/tests/test_tool_validation.py @@ -108,6 +108,19 @@ def test_exec_extract_absolute_paths_captures_posix_absolute_paths() -> None: assert "/tmp/out.txt" in paths +def test_exec_extract_absolute_paths_captures_home_paths() -> None: + cmd = "cat ~/.nanobot/config.json > ~/out.txt" + paths = ExecTool._extract_absolute_paths(cmd) + assert "~/.nanobot/config.json" in paths + assert "~/out.txt" in paths + + +def test_exec_guard_blocks_home_path_outside_workspace(tmp_path) -> None: + tool = ExecTool(restrict_to_workspace=True) + error = tool._guard_command("cat ~/.nanobot/config.json", str(tmp_path)) + assert error == "Error: Command blocked by safety guard (path outside working dir)" + + # --- cast_params tests --- From 9d0db072a38123d6433156bd0da321ef213ab064 Mon Sep 17 00:00:00 2001 From: Re-bin Date: Wed, 11 Mar 2026 15:43:04 +0000 Subject: [PATCH 2/2] fix: guard quoted home paths in shell tool --- nanobot/agent/tools/shell.py | 4 ++-- tests/test_tool_validation.py | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index 4726e3c..b650930 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -155,6 +155,6 @@ class ExecTool(Tool): @staticmethod def _extract_absolute_paths(command: str) -> list[str]: win_paths = re.findall(r"[A-Za-z]:\\[^\s\"'|><;]+", command) # Windows: C:\... - posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", command) # POSIX: /absolute only - home_paths = re.findall(r"(?:^|[\s|>])(~[^\s\"'>;|<]*)", command) # POSIX/Windows home shortcut: ~ + posix_paths = re.findall(r"(?:^|[\s|>'\"])(/[^\s\"'>;|<]+)", command) # POSIX: /absolute only + home_paths = re.findall(r"(?:^|[\s|>'\"])(~[^\s\"'>;|<]*)", command) # POSIX/Windows home shortcut: ~ return win_paths + posix_paths + home_paths diff --git a/tests/test_tool_validation.py b/tests/test_tool_validation.py index cf648bf..e67acbf 100644 --- a/tests/test_tool_validation.py +++ b/tests/test_tool_validation.py @@ -115,12 +115,25 @@ def test_exec_extract_absolute_paths_captures_home_paths() -> None: assert "~/out.txt" in paths +def test_exec_extract_absolute_paths_captures_quoted_paths() -> None: + cmd = 'cat "/tmp/data.txt" "~/.nanobot/config.json"' + paths = ExecTool._extract_absolute_paths(cmd) + assert "/tmp/data.txt" in paths + assert "~/.nanobot/config.json" in paths + + def test_exec_guard_blocks_home_path_outside_workspace(tmp_path) -> None: tool = ExecTool(restrict_to_workspace=True) error = tool._guard_command("cat ~/.nanobot/config.json", str(tmp_path)) assert error == "Error: Command blocked by safety guard (path outside working dir)" +def test_exec_guard_blocks_quoted_home_path_outside_workspace(tmp_path) -> None: + tool = ExecTool(restrict_to_workspace=True) + error = tool._guard_command('cat "~/.nanobot/config.json"', str(tmp_path)) + assert error == "Error: Command blocked by safety guard (path outside working dir)" + + # --- cast_params tests ---