From fffc04190f397822e4b31dfe9172bcbaa1a50718 Mon Sep 17 00:00:00 2001 From: HAL9000 Date: Sat, 18 Apr 2026 23:10:24 +0000 Subject: [PATCH 1/4] fix(tui): use ShellSafetyService regex patterns in looks_dangerous() Replace naive substring matching in shell_exec.looks_dangerous() with regex-based ShellSafetyService to prevent bypass via spacing variations. - Use ShellSafetyService for danger detection instead of looks_dangerous() - Update confirm_dangerous callback to receive DangerousCommandWarning - Add BDD scenarios for spacing-variant dangerous commands - Deprecate looks_dangerous() function for backward compatibility Closes #8466 --- .../steps/tui_shell_exec_coverage_steps.py | 6 +- features/tui_shell_exec_coverage.feature | 35 ++++++++++++ src/cleveragents/tui/input/modes.py | 2 +- src/cleveragents/tui/input/shell_exec.py | 56 +++++++++++++------ 4 files changed, 80 insertions(+), 19 deletions(-) diff --git a/features/steps/tui_shell_exec_coverage_steps.py b/features/steps/tui_shell_exec_coverage_steps.py index 24376af07..6f73f5771 100644 --- a/features/steps/tui_shell_exec_coverage_steps.py +++ b/features/steps/tui_shell_exec_coverage_steps.py @@ -78,8 +78,9 @@ def step_confirm_callback_true(context): """Create a confirm_dangerous callback that approves dangerous commands.""" context.confirm_called = False - def confirm_true(cmd): + def confirm_true(warning): context.confirm_called = True + context.confirm_warning = warning return True context.confirm_callback = confirm_true @@ -90,8 +91,9 @@ def step_confirm_callback_false(context): """Create a confirm_dangerous callback that rejects dangerous commands.""" context.confirm_called = False - def confirm_false(cmd): + def confirm_false(warning): context.confirm_called = True + context.confirm_warning = warning return False context.confirm_callback = confirm_false diff --git a/features/tui_shell_exec_coverage.feature b/features/tui_shell_exec_coverage.feature index 742c5c183..c062b12fc 100644 --- a/features/tui_shell_exec_coverage.feature +++ b/features/tui_shell_exec_coverage.feature @@ -45,3 +45,38 @@ Feature: TUI Shell Exec Coverage When I run a shell command "sleep 999" with a 1 second timeout Then the shell result exit code should be 124 And the shell result stderr should contain "command timed out after" + + Scenario: Dangerous command with double space is blocked + When I run a shell command "rm -rf /" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: Dangerous command with tab character is blocked + When I run a shell command "rm -rf /" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: Dangerous command with sudo prefix is blocked + When I run a shell command "sudo rm -rf /" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: Dangerous command with mixed spacing is blocked + When I run a shell command "rm -rf /" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: Git push force with spacing variations is blocked + When I run a shell command "git push --force" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: mkfs with spacing variations is blocked + When I run a shell command "mkfs.ext4 /dev/sdb1" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" + + Scenario: dd if= with spacing variations is blocked + When I run a shell command "dd if=/dev/zero" + Then the shell result exit code should be 1 + And the shell result stderr should be "blocked dangerous shell command" diff --git a/src/cleveragents/tui/input/modes.py b/src/cleveragents/tui/input/modes.py index d1d79641d..0db693338 100644 --- a/src/cleveragents/tui/input/modes.py +++ b/src/cleveragents/tui/input/modes.py @@ -42,7 +42,7 @@ class InputModeRouter: self, command_handler: Callable[[str], str], *, - shell_confirm: Callable[[str], bool] | None = None, + shell_confirm: Callable[[DangerousCommandWarning], bool] | None = None, shell_timeout_seconds: int = 30, shell_safety: ShellSafetyService | None = None, ) -> None: diff --git a/src/cleveragents/tui/input/shell_exec.py b/src/cleveragents/tui/input/shell_exec.py index 9e57af77e..e95025eec 100644 --- a/src/cleveragents/tui/input/shell_exec.py +++ b/src/cleveragents/tui/input/shell_exec.py @@ -7,6 +7,11 @@ import subprocess from collections.abc import Callable from dataclasses import dataclass +from cleveragents.tui.shell_safety import ( + DangerousCommandWarning, + ShellSafetyService, +) + @dataclass(slots=True, frozen=True) class ShellResult: @@ -19,7 +24,14 @@ class ShellResult: def looks_dangerous(command: str) -> bool: - """Best-effort dangerous command detector.""" + """Best-effort dangerous command detector. + + .. deprecated:: + Use :class:`~cleveragents.tui.shell_safety.ShellSafetyService` + instead. This function uses naive substring matching that can be + bypassed by spacing variations and is kept only for backward + compatibility. + """ lowered = command.strip().lower() patterns = ( "rm -rf /", @@ -34,7 +46,7 @@ def looks_dangerous(command: str) -> bool: def run_shell_command( command: str, *, - confirm_dangerous: Callable[[str], bool] | None = None, + confirm_dangerous: Callable[[DangerousCommandWarning], bool] | None = None, timeout_seconds: int = 30, ) -> ShellResult: """Execute shell command with basic safeguards. @@ -43,6 +55,16 @@ def run_shell_command( - Shell mode is a convenience feature for local development, not a sandbox. - We block obviously dangerous command patterns unless explicitly confirmed. - We apply a timeout to avoid hanging the UI event loop indefinitely. + + Args: + command: The shell command to execute. + confirm_dangerous: Optional callback invoked when a dangerous command + is detected. Receives a :class:`DangerousCommandWarning` and + should return ``True`` to allow execution or ``False`` to block. + timeout_seconds: Maximum time in seconds to allow the command to run. + + Returns: + A :class:`ShellResult` with the command execution outcome. """ if not command.strip(): return ShellResult( @@ -55,20 +77,22 @@ def run_shell_command( stdout="", stderr="shell mode is disabled", ) - if confirm_dangerous is not None and not confirm_dangerous(command): - return ShellResult( - command=command, - exit_code=1, - stdout="", - stderr="blocked by shell safety policy", - ) - if looks_dangerous(command) and confirm_dangerous is None: - return ShellResult( - command=command, - exit_code=1, - stdout="", - stderr="blocked dangerous shell command", - ) + + # Use ShellSafetyService for regex-based danger detection + service = ShellSafetyService() + check_result = service.check_command(command) + + if not check_result.allowed: + confirmed = False + if confirm_dangerous is not None and check_result.warning is not None: + confirmed = confirm_dangerous(check_result.warning) + if not confirmed: + return ShellResult( + command=command, + exit_code=1, + stdout="", + stderr="blocked dangerous shell command", + ) try: proc = subprocess.run( command, -- 2.52.0 From 831bace36e1e7fe1be523a7b5eb3ae61c526072b Mon Sep 17 00:00:00 2001 From: controller-ci-rerun Date: Thu, 11 Jun 2026 00:07:56 -0400 Subject: [PATCH 2/4] chore: re-trigger CI [controller] -- 2.52.0 From 8ce8133d2a6a5e2e62331568faf89b68a1f43092 Mon Sep 17 00:00:00 2001 From: controller-ci-rerun Date: Mon, 15 Jun 2026 14:52:51 -0400 Subject: [PATCH 3/4] chore: re-trigger CI [controller] -- 2.52.0 From 29c5a2edfa04b763ff436cf23d97c7ccc5bb0f06 Mon Sep 17 00:00:00 2001 From: CleverAgents Bot Date: Thu, 18 Jun 2026 06:49:56 -0400 Subject: [PATCH 4/4] fix(tui): align shell safety regex execution --- features/tui_shell_exec_coverage.feature | 2 +- src/cleveragents/tui/app.py | 9 ++++++++- src/cleveragents/tui/input/modes.py | 4 +++- src/cleveragents/tui/input/shell_exec.py | 22 +++++++++------------- 4 files changed, 21 insertions(+), 16 deletions(-) diff --git a/features/tui_shell_exec_coverage.feature b/features/tui_shell_exec_coverage.feature index c062b12fc..e312c9963 100644 --- a/features/tui_shell_exec_coverage.feature +++ b/features/tui_shell_exec_coverage.feature @@ -38,7 +38,7 @@ Feature: TUI Shell Exec Coverage Given a confirm_dangerous callback that returns False When I run a dangerous command "rm -rf /" with the callback Then the shell result exit code should be 1 - And the shell result stderr should be "blocked by shell safety policy" + And the shell result stderr should be "blocked dangerous shell command" Scenario: Command that exceeds timeout returns timeout result Given subprocess run is mocked to raise TimeoutExpired diff --git a/src/cleveragents/tui/app.py b/src/cleveragents/tui/app.py index 6dceed3d7..0690314e4 100644 --- a/src/cleveragents/tui/app.py +++ b/src/cleveragents/tui/app.py @@ -625,10 +625,17 @@ if _TEXTUAL_AVAILABLE: ) worker.done_callback = _on_llm_done - def _confirm_dangerous_shell(self, command: str) -> bool: + def _confirm_dangerous_shell( + self, warning_or_command: DangerousCommandWarning | str + ) -> bool: if self._shell_safety is not None: # The ShellSafetyService already provided the execution verdict. return True + command = ( + warning_or_command.command + if isinstance(warning_or_command, DangerousCommandWarning) + else warning_or_command + ) if not looks_dangerous(command): return True return self._allow_dangerous_shell diff --git a/src/cleveragents/tui/input/modes.py b/src/cleveragents/tui/input/modes.py index 0db693338..2ebdf9fe0 100644 --- a/src/cleveragents/tui/input/modes.py +++ b/src/cleveragents/tui/input/modes.py @@ -85,7 +85,9 @@ class InputModeRouter: warning = safety_result.warning allowed = safety_result.allowed - def _safety_gate(_cmd: str, *, allow: bool = allowed) -> bool: + def _safety_gate( + _warning: DangerousCommandWarning, *, allow: bool = allowed + ) -> bool: return allow confirm = _safety_gate diff --git a/src/cleveragents/tui/input/shell_exec.py b/src/cleveragents/tui/input/shell_exec.py index e95025eec..8567affd1 100644 --- a/src/cleveragents/tui/input/shell_exec.py +++ b/src/cleveragents/tui/input/shell_exec.py @@ -9,6 +9,7 @@ from dataclasses import dataclass from cleveragents.tui.shell_safety import ( DangerousCommandWarning, + ShellDangerLevel, ShellSafetyService, ) @@ -28,19 +29,13 @@ def looks_dangerous(command: str) -> bool: .. deprecated:: Use :class:`~cleveragents.tui.shell_safety.ShellSafetyService` - instead. This function uses naive substring matching that can be - bypassed by spacing variations and is kept only for backward - compatibility. + instead. This function delegates to the regex-based shell safety + service and is kept only for backward compatibility. """ - lowered = command.strip().lower() - patterns = ( - "rm -rf /", - "git push --force", - "mkfs.", - "dd if=", - ":(){:|:&};:", + service = ShellSafetyService( + block_level=ShellDangerLevel.LOW, ) - return any(pattern in lowered for pattern in patterns) + return not service.check_command(command).allowed def run_shell_command( @@ -78,8 +73,9 @@ def run_shell_command( stderr="shell mode is disabled", ) - # Use ShellSafetyService for regex-based danger detection - service = ShellSafetyService() + service = ShellSafetyService( + block_level=ShellDangerLevel.LOW, + ) check_result = service.check_command(command) if not check_result.allowed: -- 2.52.0