fix: allow git commands in workspace subdirectories

The shell safety guard in _guard_command() checked extracted absolute
paths only against cwd_path (the command's working directory).  When
cwd was a subdirectory like ~/.nanobot/workspace/obsidian_notes, any
absolute path referencing the broader workspace (e.g. a sibling
directory or the workspace root itself) was incorrectly blocked with
"path outside working dir".

Fix: pass the workspace_root from _prepare_command() into
_guard_command() and check absolute paths against it as a fallback
when they are outside cwd_path.  Paths truly outside the workspace
root are still blocked.

Added tests:
- test_exec_allows_workspace_paths_from_subdirectory
- test_exec_blocks_outside_paths_from_subdirectory
This commit is contained in:
Haisam
2026-06-18 00:02:58 +08:00
committed by Xubin Ren
parent 4219911423
commit bfe5b64022
2 changed files with 58 additions and 2 deletions
+12 -2
View File
@@ -397,6 +397,7 @@ class ExecTool(Tool):
command,
cwd,
restrict_to_workspace=access.restrict_to_workspace,
workspace_root=workspace_root,
)
if guard_error:
return guard_error
@@ -591,6 +592,7 @@ class ExecTool(Tool):
cwd: str,
*,
restrict_to_workspace: bool | None = None,
workspace_root: str | None = None,
) -> str | None:
"""Best-effort safety guard for potentially destructive commands."""
cmd = command.strip()
@@ -629,6 +631,11 @@ class ExecTool(Tool):
)
cwd_path = Path(cwd).resolve()
resolved_workspace = (
Path(workspace_root).expanduser().resolve()
if workspace_root
else None
)
for raw in self._extract_absolute_paths(cmd):
try:
@@ -646,10 +653,13 @@ class ExecTool(Tool):
continue
media_path = get_media_dir().resolve()
if p.is_absolute() and not (
allowed = (
is_path_within(p, cwd_path)
or is_path_within(p, media_path)
):
)
if not allowed and resolved_workspace is not None:
allowed = is_path_within(p, resolved_workspace)
if p.is_absolute() and not allowed:
return (
"Error: Command blocked by safety guard (path outside working dir)"
+ _WORKSPACE_BOUNDARY_NOTE
+46
View File
@@ -349,3 +349,49 @@ def test_exec_allows_format_in_url_and_args(command):
tool = ExecTool()
result = tool._guard_command(command, "/tmp")
assert result is None
# --- workspace_root allows paths inside workspace but outside cwd ----------
def test_exec_allows_workspace_paths_from_subdirectory(tmp_path):
"""Absolute paths inside the workspace root must be allowed even when cwd
is a subdirectory. This is the scenario reported in the issue: git
commands in ``~/.nanobot/workspace/obsidian_notes`` reference paths
under the broader workspace that are outside the subdirectory cwd."""
workspace = tmp_path / "workspace"
subdir = workspace / "obsidian_notes"
subdir.mkdir(parents=True)
sibling = workspace / "other_project"
sibling.mkdir()
tool = ExecTool(working_dir=str(workspace), restrict_to_workspace=True)
# A command run from the subdirectory that references a sibling path
# inside the workspace should be allowed.
result = tool._guard_command(
f"git clone {sibling}",
str(subdir),
workspace_root=str(workspace),
)
assert result is None
def test_exec_blocks_outside_paths_from_subdirectory(tmp_path):
"""Paths truly outside the workspace must still be blocked even when
workspace_root is provided."""
workspace = tmp_path / "workspace"
subdir = workspace / "project"
subdir.mkdir(parents=True)
outside = tmp_path / "secrets"
outside.mkdir()
tool = ExecTool(working_dir=str(workspace), restrict_to_workspace=True)
result = tool._guard_command(
f"cat {outside / 'key.pem'}",
str(subdir),
workspace_root=str(workspace),
)
assert result is not None
assert "path outside working dir" in result