Merge PR #1827: tighten shell path guard for quoted home paths
This commit is contained in:
@@ -143,7 +143,8 @@ class ExecTool(Tool):
|
|||||||
|
|
||||||
for raw in self._extract_absolute_paths(cmd):
|
for raw in self._extract_absolute_paths(cmd):
|
||||||
try:
|
try:
|
||||||
p = Path(raw.strip()).resolve()
|
expanded = os.path.expandvars(raw.strip())
|
||||||
|
p = Path(expanded).expanduser().resolve()
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
if p.is_absolute() and cwd_path not in p.parents and p != cwd_path:
|
if p.is_absolute() and cwd_path not in p.parents and p != cwd_path:
|
||||||
@@ -154,5 +155,6 @@ class ExecTool(Tool):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def _extract_absolute_paths(command: str) -> list[str]:
|
def _extract_absolute_paths(command: str) -> list[str]:
|
||||||
win_paths = re.findall(r"[A-Za-z]:\\[^\s\"'|><;]+", command) # Windows: C:\...
|
win_paths = re.findall(r"[A-Za-z]:\\[^\s\"'|><;]+", command) # Windows: C:\...
|
||||||
posix_paths = re.findall(r"(?:^|[\s|>])(/[^\s\"'>]+)", command) # POSIX: /absolute only
|
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
|
||||||
|
|||||||
@@ -108,6 +108,32 @@ def test_exec_extract_absolute_paths_captures_posix_absolute_paths() -> None:
|
|||||||
assert "/tmp/out.txt" in paths
|
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_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 ---
|
# --- cast_params tests ---
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user