mirror of
https://github.com/HKUDS/nanobot.git
synced 2026-08-12 23:29:16 +03:00
fix(tools): handle redirection and grouping delimiters in ExecTool path guard
This commit is contained in:
@@ -1010,8 +1010,15 @@ class ExecTool(Tool):
|
|||||||
r"(?<![A-Za-z])(?:[A-Za-z]:[^\s\"'|><;]*|\\\\[^\s\"'|><;]+(?:\\[^\s\"'|><;]+)*)",
|
r"(?<![A-Za-z])(?:[A-Za-z]:[^\s\"'|><;]*|\\\\[^\s\"'|><;]+(?:\\[^\s\"'|><;]+)*)",
|
||||||
command
|
command
|
||||||
)
|
)
|
||||||
posix_paths = re.findall(r"(?:^|[\s|>='\"])(/[^\s\"'>;|<]+)", command) # POSIX: /absolute only
|
posix_paths = [
|
||||||
home_paths = re.findall(r"(?:^|[\s>='\"])(~[/+][^\s\"'>;|<]*)", command) # POSIX/Windows home shortcut: ~/ or ~+
|
p.rstrip(");},")
|
||||||
|
for p in re.findall(r"(?:^|[\s|><='\"({,:])(/[^\"'>;|<()\s]+)", command)
|
||||||
|
if not p.startswith("//")
|
||||||
|
]
|
||||||
|
home_paths = [
|
||||||
|
p.rstrip(");},")
|
||||||
|
for p in re.findall(r"(?:^|[\s|><='\"({,:])(~[/+][^\"'>;|<()\s]+)", command)
|
||||||
|
]
|
||||||
return win_paths + posix_paths + home_paths
|
return win_paths + posix_paths + home_paths
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
|
|||||||
@@ -159,6 +159,7 @@ async def test_exec_blocks_chained_internal_url():
|
|||||||
"cp /tmp/x memory/.dream_cursor",
|
"cp /tmp/x memory/.dream_cursor",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_exec_blocks_writes_to_history_jsonl(command):
|
def test_exec_blocks_writes_to_history_jsonl(command):
|
||||||
"""Direct writes to history.jsonl / .dream_cursor must be blocked (#2989)."""
|
"""Direct writes to history.jsonl / .dream_cursor must be blocked (#2989)."""
|
||||||
tool = ExecTool()
|
tool = ExecTool()
|
||||||
@@ -179,6 +180,7 @@ def test_exec_blocks_writes_to_history_jsonl(command):
|
|||||||
"echo history.jsonl",
|
"echo history.jsonl",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_exec_allows_reads_of_history_jsonl(command):
|
def test_exec_allows_reads_of_history_jsonl(command):
|
||||||
"""Read-only access to history.jsonl must still be allowed."""
|
"""Read-only access to history.jsonl must still be allowed."""
|
||||||
tool = ExecTool()
|
tool = ExecTool()
|
||||||
@@ -274,6 +276,7 @@ async def test_exec_ignores_workspace_check_when_not_restricted(tmp_path):
|
|||||||
"cat /dev/fd/3",
|
"cat /dev/fd/3",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_exec_allows_benign_device_targets_inside_workspace(tmp_path, command):
|
def test_exec_allows_benign_device_targets_inside_workspace(tmp_path, command):
|
||||||
workspace = tmp_path / "workspace"
|
workspace = tmp_path / "workspace"
|
||||||
workspace.mkdir()
|
workspace.mkdir()
|
||||||
@@ -426,6 +429,7 @@ def test_exec_bwrap_bind_parent_does_not_widen_workspace_guard(tmp_path, monkeyp
|
|||||||
"|format",
|
"|format",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_exec_blocks_format_command(command):
|
def test_exec_blocks_format_command(command):
|
||||||
"""The Windows ``format`` disk command must be denied."""
|
"""The Windows ``format`` disk command must be denied."""
|
||||||
tool = ExecTool()
|
tool = ExecTool()
|
||||||
@@ -445,6 +449,7 @@ def test_exec_blocks_format_command(command):
|
|||||||
"echo reformat",
|
"echo reformat",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_exec_allows_format_in_url_and_args(command):
|
def test_exec_allows_format_in_url_and_args(command):
|
||||||
"""``format`` inside URL parameters or as a non-command arg must be allowed."""
|
"""``format`` inside URL parameters or as a non-command arg must be allowed."""
|
||||||
tool = ExecTool()
|
tool = ExecTool()
|
||||||
@@ -496,3 +501,21 @@ def test_exec_blocks_outside_paths_from_subdirectory(tmp_path):
|
|||||||
)
|
)
|
||||||
assert result is not None
|
assert result is not None
|
||||||
assert "path outside working dir" in result
|
assert "path outside working dir" in result
|
||||||
|
|
||||||
|
def test_exec_blocks_outside_paths_with_redirection_and_delimiters(tmp_path):
|
||||||
|
workspace = tmp_path / "workspace"
|
||||||
|
workspace.mkdir()
|
||||||
|
outside = tmp_path / "secrets"
|
||||||
|
outside.mkdir()
|
||||||
|
|
||||||
|
tool = ExecTool(working_dir=str(workspace), restrict_to_workspace=True)
|
||||||
|
|
||||||
|
for cmd in (
|
||||||
|
f"cat<{outside / 'key.pem'}",
|
||||||
|
f"cat <{outside / 'key.pem'}",
|
||||||
|
f"({outside / 'key.pem'})",
|
||||||
|
f"cat {{{outside / 'key.pem'}}}",
|
||||||
|
):
|
||||||
|
result = tool._guard_command(cmd, str(workspace), workspace_root=str(workspace))
|
||||||
|
assert result is not None, f"Expected {cmd} to be blocked"
|
||||||
|
assert "path outside working dir" in result
|
||||||
|
|||||||
Reference in New Issue
Block a user