From d5658dbc91db11a90e14dcc9f38cb358f761cd0e Mon Sep 17 00:00:00 2001 From: chengyongru Date: Wed, 1 Jul 2026 15:16:59 +0800 Subject: [PATCH] fix: preserve background operator in allowlist segments Maintainer edit: keep a top-level trailing '&' in the segment being matched so background execution cannot be checked as if the ampersand were absent. Redirection forms like 2>&1 and &> remain untouched. --- nanobot/agent/tools/shell.py | 1 + tests/tools/test_exec_allow_patterns.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/nanobot/agent/tools/shell.py b/nanobot/agent/tools/shell.py index 565b38eb..4950925a 100644 --- a/nanobot/agent/tools/shell.py +++ b/nanobot/agent/tools/shell.py @@ -882,6 +882,7 @@ class ExecTool(Tool): elif ch == "&" and not ( (i > 0 and command[i - 1] in "<>") or command.startswith("&>", i) ): + current.append(ch) operator_len = 1 elif ch in {";", "|"}: operator_len = 1 diff --git a/tests/tools/test_exec_allow_patterns.py b/tests/tools/test_exec_allow_patterns.py index 12a35059..0a10d5ec 100644 --- a/tests/tools/test_exec_allow_patterns.py +++ b/tests/tools/test_exec_allow_patterns.py @@ -76,6 +76,15 @@ def test_guard_allow_patterns_block_single_ampersand_chained_segment(): assert "allowlist" in result.lower() +def test_guard_allow_patterns_preserve_trailing_background_operator(): + tool = ExecTool(allow_patterns=[r"echo\s+allowlisted"]) + + result = tool._guard_command("echo allowlisted &", "/tmp") + + assert result is not None + assert "allowlist" in result.lower() + + def test_guard_allow_patterns_keep_fd_redirection_ampersand(): tool = ExecTool(allow_patterns=[r"echo\s+allowlisted\s+2>&1"])