forked from cleveragents/cleveragents-core
docs(tui): add shell safety, permission question widget, and first-run docs
Add reference documentation for three new TUI features: - docs/reference/tui_shell_safety.md: Full reference for the shell danger detection subsystem (DangerousPatternDetector, ShellDangerLevel, DangerousPattern, DangerousCommandWarning, DEFAULT_PATTERNS registry). Covers all built-in patterns across CRITICAL/HIGH/MEDIUM/LOW levels, usage examples, and custom pattern extension. - docs/reference/tui_permission_question.md: Full reference for the inline PermissionQuestionWidget (issue #997). Documents InlinePermissionQuestion, PermissionRequestType, PermissionDecision, render_permission_question(), PermissionDecisionEvent, and PermissionQuestionWidget with key bindings and usage examples. - docs/reference/tui.md: Extended with First-Run Experience section (ActorSelectionOverlay, first_run helpers), Inline Permission Questions section, shell danger detection note in Shell Mode, updated module table, and links to new reference pages.
This commit is contained in:
@@ -97,6 +97,14 @@ captured and displayed in the conversation area.
|
||||
environment variable. Set it to `1` or `true` to permit shell commands.
|
||||
When unset, shell commands are blocked.
|
||||
|
||||
Before execution, the command is scanned by the **shell danger detection**
|
||||
subsystem. If a dangerous pattern is matched (e.g. `rm -rf /`, fork bombs,
|
||||
pipe-to-shell), a warning overlay is shown with the danger level and a
|
||||
description of the risk. The user can cancel or proceed.
|
||||
|
||||
See [Shell Danger Detection](tui_shell_safety.md) for the full reference,
|
||||
including the built-in pattern registry and how to add custom patterns.
|
||||
|
||||
## Help Panel (F1)
|
||||
|
||||
Press `F1` at any time to toggle the context-sensitive help panel. The panel
|
||||
@@ -352,6 +360,105 @@ Key modules:
|
||||
| `tui/permissions/models.py` | `ToolPermissionRequest`, `PermissionDecision`, `DiffDisplayMode` |
|
||||
| `tui/permissions/screen.py` | `PermissionsScreen` — split-pane diff view for tool permission requests |
|
||||
| `tui/permissions/service.py` | `PermissionRequestService` — request queue and session-scoped decisions |
|
||||
| `tui/widgets/permission_question.py` | `PermissionQuestionWidget` — inline single-file permission request widget |
|
||||
| `tui/widgets/actor_selection_overlay.py` | `ActorSelectionOverlay` — first-run actor selection overlay |
|
||||
| `tui/shell_safety/pattern_detector.py` | `DangerousPatternDetector` — shell command danger detection |
|
||||
| `tui/shell_safety/pattern_registry.py` | `DEFAULT_PATTERNS` — built-in dangerous shell pattern registry |
|
||||
| `tui/first_run.py` | `is_first_run()`, `create_default_persona_for_actor()` — first-run helpers |
|
||||
|
||||
## First-Run Experience
|
||||
|
||||
On first launch — when no personas are configured — the TUI displays the
|
||||
`ActorSelectionOverlay` in the centre of the screen. The overlay guides the
|
||||
user to select an actor from a curated list:
|
||||
|
||||
- `anthropic/claude-4-sonnet` *(recommended)*
|
||||
- `anthropic/claude-4-opus`
|
||||
- `openai/gpt-4o`
|
||||
- `openai/o3`
|
||||
- `google/gemini-2`
|
||||
|
||||
**Key bindings inside the overlay:**
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `j` / `↓` | Move selection down |
|
||||
| `k` / `↑` | Move selection up |
|
||||
| `/` | Enter search mode to filter actors |
|
||||
| `Enter` | Confirm the highlighted actor |
|
||||
|
||||
After confirmation, a `"default"` persona is created and the overlay is
|
||||
dismissed. Subsequent launches restore the last active persona from
|
||||
`~/.config/cleveragents/tui-state.yaml`.
|
||||
|
||||
**Module**: `cleveragents.tui.widgets.actor_selection_overlay`
|
||||
|
||||
The `ActorSelectionOverlay` widget exposes:
|
||||
|
||||
| Property / Method | Description |
|
||||
|-------------------|-------------|
|
||||
| `actors` | Full (unfiltered) actor list |
|
||||
| `filtered_actors` | Currently filtered actor list |
|
||||
| `selected_index` | Zero-based index of the highlighted actor |
|
||||
| `search_query` | Active search filter string |
|
||||
| `confirmed` | `True` if the user has confirmed a selection |
|
||||
| `selected_actor` | Confirmed actor name, or `None` if not yet confirmed |
|
||||
| `show(actors?)` | Display the overlay, optionally overriding the actor list |
|
||||
| `hide()` | Hide the overlay |
|
||||
| `move_up()` / `move_down()` | Navigate the list |
|
||||
| `set_search(query)` | Apply a substring filter (case-insensitive) |
|
||||
| `confirm()` | Confirm the highlighted actor; returns the actor name or `None` |
|
||||
|
||||
The first-run detection helper is in `cleveragents.tui.first_run`:
|
||||
|
||||
```python
|
||||
from cleveragents.tui.first_run import is_first_run, create_default_persona_for_actor
|
||||
|
||||
if is_first_run(registry):
|
||||
# Show ActorSelectionOverlay, then:
|
||||
persona = create_default_persona_for_actor(registry, "openai/gpt-4o")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Inline Permission Questions
|
||||
|
||||
For **single-file** permission requests, the TUI renders a
|
||||
`PermissionQuestionWidget` inline in the conversation stream rather than
|
||||
pushing the full `PermissionsScreen`.
|
||||
|
||||
```
|
||||
Permission Required
|
||||
|
||||
The actor wants to write to:
|
||||
src/api/main.py
|
||||
|
||||
❯ a Allow once
|
||||
A Allow always (this session)
|
||||
r Reject once
|
||||
R Reject always (this session)
|
||||
|
||||
Press v to open full PermissionsScreen with diff view
|
||||
```
|
||||
|
||||
**Key bindings:**
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `a` | Allow once |
|
||||
| `A` | Allow always (this session) |
|
||||
| `r` | Reject once |
|
||||
| `R` | Reject always (this session) |
|
||||
| `↑` / `↓` | Navigate options |
|
||||
| `Enter` | Confirm highlighted option |
|
||||
| `v` | Open full `PermissionsScreen` with diff view |
|
||||
|
||||
A `PermissionDecisionEvent` is emitted when the user makes a decision.
|
||||
|
||||
See [Permission Question Widget](tui_permission_question.md) for the full
|
||||
API reference.
|
||||
|
||||
---
|
||||
|
||||
## Permissions Screen
|
||||
|
||||
@@ -387,6 +494,8 @@ See [`tui_thought_block.md`](tui_thought_block.md) for the full API reference.
|
||||
- [ADR-045: TUI Persona System](../adr/ADR-045-tui-persona-system.md)
|
||||
- [ADR-046: TUI Reference and Command System](../adr/ADR-046-tui-reference-and-command-system.md)
|
||||
- [TUI Permissions Screen](tui_permissions.md)
|
||||
- [TUI Permission Question Widget](tui_permission_question.md)
|
||||
- [TUI Shell Danger Detection](tui_shell_safety.md)
|
||||
- [TUI Thought Blocks](tui_thought_block.md)
|
||||
- [Session CLI Reference](session_cli.md)
|
||||
- [Output Rendering Framework](output_rendering.md)
|
||||
|
||||
@@ -0,0 +1,246 @@
|
||||
# TUI — Permission Question Widget
|
||||
|
||||
The Permission Question Widget renders inline permission requests directly in
|
||||
the conversation stream for **single-file operations**. For multi-file
|
||||
operations, the full [`PermissionsScreen`](tui_permissions.md) is used instead.
|
||||
|
||||
Introduced in the [Unreleased] milestone (issue #997).
|
||||
See also [`tui.md`](tui.md) for the overall TUI architecture.
|
||||
|
||||
**Module**: `cleveragents.tui.widgets.permission_question`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When an actor requests a single-file operation (write, delete, read, shell
|
||||
execution, or network access), the TUI renders a `PermissionQuestionWidget`
|
||||
inline in the conversation stream. The user can respond with single-key
|
||||
shortcuts or navigate with arrow keys.
|
||||
|
||||
```
|
||||
Permission Required
|
||||
|
||||
The actor wants to write to:
|
||||
src/api/main.py
|
||||
|
||||
❯ a Allow once
|
||||
A Allow always (this session)
|
||||
r Reject once
|
||||
R Reject always (this session)
|
||||
|
||||
Press v to open full PermissionsScreen with diff view
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Models
|
||||
|
||||
### `PermissionRequestType`
|
||||
|
||||
**Module**: `cleveragents.domain.models.core.inline_permission_question`
|
||||
|
||||
`StrEnum` classifying the type of operation being requested.
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `file_write` | Actor wants to write to a file |
|
||||
| `file_delete` | Actor wants to delete a file |
|
||||
| `file_read` | Actor wants to read a file |
|
||||
| `shell_exec` | Actor wants to execute a shell command |
|
||||
| `network` | Actor wants to access the network |
|
||||
|
||||
### `PermissionDecision`
|
||||
|
||||
**Module**: `cleveragents.domain.models.core.inline_permission_question`
|
||||
|
||||
`StrEnum` for the user's decision on a permission request.
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `allow_once` | Allow this specific operation once |
|
||||
| `allow_always` | Allow all operations from this actor for the session |
|
||||
| `reject_once` | Reject this specific operation once |
|
||||
| `reject_always` | Reject all operations from this actor for the session |
|
||||
|
||||
### `InlinePermissionQuestion`
|
||||
|
||||
**Module**: `cleveragents.domain.models.core.inline_permission_question`
|
||||
|
||||
Pydantic model representing a single-file permission request.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `file_path` | `str` | — | Path of the file the actor wants to operate on (non-empty) |
|
||||
| `request_type` | `PermissionRequestType` | — | Type of operation being requested |
|
||||
| `diff_content` | `str` | `""` | Unified diff content showing proposed changes (may be empty) |
|
||||
| `actor_name` | `str` | `"actor"` | Name of the actor requesting permission |
|
||||
|
||||
**Properties:**
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `has_diff` | `bool` | `True` when `diff_content` is non-empty after stripping whitespace |
|
||||
|
||||
**Methods:**
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `description_line()` | `str` | Human-readable one-line description (e.g. `"The actor wants to write to:"`) |
|
||||
|
||||
---
|
||||
|
||||
## `render_permission_question`
|
||||
|
||||
**Module**: `cleveragents.tui.widgets.permission_question`
|
||||
|
||||
Pure rendering helper that produces the widget's text content.
|
||||
|
||||
```python
|
||||
def render_permission_question(
|
||||
question: InlinePermissionQuestion,
|
||||
selected_index: int = 0,
|
||||
*,
|
||||
show_diff: bool = False,
|
||||
) -> str:
|
||||
```
|
||||
|
||||
| Parameter | Type | Default | Description |
|
||||
|-----------|------|---------|-------------|
|
||||
| `question` | `InlinePermissionQuestion` | — | The permission question to render |
|
||||
| `selected_index` | `int` | `0` | Index of the currently highlighted option (0–3) |
|
||||
| `show_diff` | `bool` | `False` | When `True`, appends the diff content below the options |
|
||||
|
||||
Returns a multi-line string suitable for display in a Textual `Static` widget.
|
||||
|
||||
---
|
||||
|
||||
## `PermissionDecisionEvent`
|
||||
|
||||
**Module**: `cleveragents.tui.widgets.permission_question`
|
||||
|
||||
Emitted when the user makes a permission decision. The host application
|
||||
listens for this event to act on the user's choice.
|
||||
|
||||
| Attribute | Type | Description |
|
||||
|-----------|------|-------------|
|
||||
| `question` | `InlinePermissionQuestion` | The original permission question |
|
||||
| `decision` | `PermissionDecision` | The decision made by the user |
|
||||
|
||||
---
|
||||
|
||||
## `PermissionQuestionWidget`
|
||||
|
||||
**Module**: `cleveragents.tui.widgets.permission_question`
|
||||
|
||||
Textual `Static` subclass that renders an inline permission question in the
|
||||
conversation stream.
|
||||
|
||||
### Constructor
|
||||
|
||||
```python
|
||||
PermissionQuestionWidget(question: InlinePermissionQuestion, *args, **kwargs)
|
||||
```
|
||||
|
||||
### Properties
|
||||
|
||||
| Property | Type | Description |
|
||||
|----------|------|-------------|
|
||||
| `question` | `InlinePermissionQuestion` | The associated permission question |
|
||||
| `selected_index` | `int` | Index of the currently highlighted option |
|
||||
| `decision` | `PermissionDecision \| None` | The decision if made, else `None` |
|
||||
| `open_full_screen` | `bool` | `True` if the user pressed `v` to open `PermissionsScreen` |
|
||||
|
||||
### Navigation
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `move_up()` | `None` | Move the selection cursor up by one option (wraps) |
|
||||
| `move_down()` | `None` | Move the selection cursor down by one option (wraps) |
|
||||
|
||||
### Key Handling
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `handle_key(key)` | `PermissionDecisionEvent \| None` | Process a key press; returns a decision event when resolved |
|
||||
|
||||
**Supported keys:**
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `↑` / `up` | Move selection up |
|
||||
| `↓` / `down` | Move selection down |
|
||||
| `Enter` | Confirm the currently highlighted option |
|
||||
| `a` | Allow once |
|
||||
| `A` | Allow always (this session) |
|
||||
| `r` | Reject once |
|
||||
| `R` | Reject always (this session) |
|
||||
| `v` | Set `open_full_screen = True` (host opens `PermissionsScreen`) |
|
||||
|
||||
---
|
||||
|
||||
## Usage Example
|
||||
|
||||
```python
|
||||
from cleveragents.domain.models.core.inline_permission_question import (
|
||||
InlinePermissionQuestion,
|
||||
PermissionDecision,
|
||||
PermissionRequestType,
|
||||
)
|
||||
from cleveragents.tui.widgets.permission_question import (
|
||||
PermissionDecisionEvent,
|
||||
PermissionQuestionWidget,
|
||||
render_permission_question,
|
||||
)
|
||||
|
||||
# Build the domain model
|
||||
question = InlinePermissionQuestion(
|
||||
file_path="src/api/main.py",
|
||||
request_type=PermissionRequestType.FILE_WRITE,
|
||||
diff_content="--- a/src/api/main.py\n+++ b/src/api/main.py\n@@ -1 +1,2 @@\n+import logging\n",
|
||||
actor_name="openai/gpt-4o",
|
||||
)
|
||||
|
||||
# Pure rendering (no Textual dependency)
|
||||
text = render_permission_question(question, selected_index=0, show_diff=True)
|
||||
print(text)
|
||||
|
||||
# Widget usage (requires Textual)
|
||||
widget = PermissionQuestionWidget(question)
|
||||
|
||||
# Simulate key presses
|
||||
event = widget.handle_key("down") # moves cursor, returns None
|
||||
event = widget.handle_key("enter") # confirms selection, returns PermissionDecisionEvent
|
||||
assert event is not None
|
||||
assert event.decision == PermissionDecision.ALLOW_ALWAYS # second option
|
||||
|
||||
# Single-key shortcut
|
||||
event = widget.handle_key("r")
|
||||
assert event.decision == PermissionDecision.REJECT_ONCE
|
||||
|
||||
# Open full screen
|
||||
widget.handle_key("v")
|
||||
assert widget.open_full_screen is True
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Relationship to PermissionsScreen
|
||||
|
||||
The `PermissionQuestionWidget` is designed for **single-file** operations
|
||||
where a compact inline display is sufficient. When:
|
||||
|
||||
- The operation involves **multiple files**, or
|
||||
- The user presses **`v`** to request a detailed diff view,
|
||||
|
||||
the host application should push the full
|
||||
[`PermissionsScreen`](tui_permissions.md) overlay instead.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [TUI Reference](tui.md) — overall TUI architecture
|
||||
- [TUI Permissions Screen](tui_permissions.md) — full-screen permission overlay for multi-file operations
|
||||
- [TUI Shell Safety](tui_shell_safety.md) — shell command danger detection
|
||||
- [Permissions](permissions.md) — permission system and role bindings
|
||||
@@ -0,0 +1,241 @@
|
||||
# TUI — Shell Danger Detection
|
||||
|
||||
The shell safety subsystem detects dangerous command patterns before execution
|
||||
in the TUI shell mode (`!` prefix). When a dangerous pattern is matched, the
|
||||
TUI surfaces a warning overlay so the user can confirm or abort before the
|
||||
command runs.
|
||||
|
||||
Introduced in the [Unreleased] milestone (issue #1003).
|
||||
See also [`tui.md`](tui.md) for the overall TUI architecture.
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety`
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
When the user types a shell command in the TUI (prefixed with `!`), the
|
||||
`DangerousPatternDetector` scans the command against a configurable registry
|
||||
of patterns before execution. If any patterns match, a warning is displayed
|
||||
with the danger level and a description of the risk.
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ⚠ Dangerous command detected │
|
||||
│ │
|
||||
│ [Critical] rm -rf / recursively deletes the entire filesystem │
|
||||
│ root. This will destroy the operating system and all data. │
|
||||
│ │
|
||||
│ Command: rm -rf / │
|
||||
│ │
|
||||
│ [c] Cancel [p] Proceed anyway │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Domain Models
|
||||
|
||||
### `ShellDangerLevel`
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety.danger_level`
|
||||
|
||||
`IntEnum` classifying the severity of a dangerous shell command. Levels are
|
||||
ordered so that numeric comparisons work naturally
|
||||
(`level >= ShellDangerLevel.HIGH`).
|
||||
|
||||
| Value | Int | Description |
|
||||
|-------|-----|-------------|
|
||||
| `LOW` | 1 | Minor risk — generally recoverable side-effects (e.g. `chmod 777` on a single file) |
|
||||
| `MEDIUM` | 2 | Moderate risk — can cause data loss or security exposure in common scenarios |
|
||||
| `HIGH` | 3 | High risk — significant, hard-to-reverse damage (e.g. `dd if=`, `mkfs`) |
|
||||
| `CRITICAL` | 4 | Critical risk — can destroy the entire system or create a fork bomb |
|
||||
|
||||
### `DangerousPattern`
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety.dangerous_pattern`
|
||||
|
||||
Immutable frozen dataclass describing a single dangerous shell pattern.
|
||||
|
||||
| Field | Type | Default | Description |
|
||||
|-------|------|---------|-------------|
|
||||
| `name` | `str` | — | Short human-readable identifier (e.g. `"rm_rf_root"`) |
|
||||
| `pattern` | `str` | — | Regular expression matching the dangerous command text |
|
||||
| `level` | `ShellDangerLevel` | — | Danger severity classification |
|
||||
| `description` | `str` | — | Human-readable explanation of the risk |
|
||||
| `case_sensitive` | `bool` | `False` | When `True`, the regex is compiled without `re.IGNORECASE` |
|
||||
|
||||
**Methods:**
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `matches(command)` | `bool` | `True` if `command` matches this pattern |
|
||||
|
||||
### `DangerousCommandWarning`
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety.warning`
|
||||
|
||||
Immutable frozen dataclass produced by `DangerousPatternDetector` when a
|
||||
dangerous command is detected.
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `command` | `str` | The original command string that triggered the warning |
|
||||
| `matched_pattern` | `DangerousPattern` | The pattern that matched |
|
||||
| `danger_level` | `ShellDangerLevel` | Convenience alias for `matched_pattern.level` |
|
||||
| `message` | `str` | Human-readable warning message for display |
|
||||
|
||||
**Class methods:**
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `from_pattern(command, pattern)` | `DangerousCommandWarning` | Construct a warning from a command and the matching pattern |
|
||||
|
||||
---
|
||||
|
||||
## `DangerousPatternDetector`
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety.pattern_detector`
|
||||
|
||||
Domain service that checks shell commands against a configurable registry of
|
||||
dangerous patterns. Ships with built-in `DEFAULT_PATTERNS` but supports full
|
||||
customisation.
|
||||
|
||||
### Constructor
|
||||
|
||||
```python
|
||||
DangerousPatternDetector(patterns: list[DangerousPattern] | None = None)
|
||||
```
|
||||
|
||||
When `patterns` is `None`, the built-in `DEFAULT_PATTERNS` are used.
|
||||
|
||||
### Registry Management
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `add_pattern(pattern)` | `None` | Append a pattern to the registry (checked in insertion order) |
|
||||
| `remove_pattern(name)` | `bool` | Remove the pattern with the given name; returns `True` if removed |
|
||||
| `replace_patterns(patterns)` | `None` | Replace the entire registry with a new list |
|
||||
| `patterns` *(property)* | `tuple[DangerousPattern, ...]` | Read-only view of the current registry |
|
||||
|
||||
### Detection
|
||||
|
||||
| Method | Returns | Description |
|
||||
|--------|---------|-------------|
|
||||
| `check(command)` | `list[DangerousCommandWarning]` | All warnings for `command` (may be empty); ordered by insertion order |
|
||||
| `check_first(command)` | `DangerousCommandWarning \| None` | First (highest-priority) warning, or `None` |
|
||||
| `is_dangerous(command)` | `bool` | `True` if `command` matches any registered pattern |
|
||||
| `max_danger_level(command)` | `ShellDangerLevel \| None` | Highest danger level matched, or `None` |
|
||||
|
||||
---
|
||||
|
||||
## Default Pattern Registry
|
||||
|
||||
**Module**: `cleveragents.tui.shell_safety.pattern_registry`
|
||||
|
||||
The `DEFAULT_PATTERNS` tuple ships with the following built-in patterns:
|
||||
|
||||
### Critical
|
||||
|
||||
| Name | Pattern | Description |
|
||||
|------|---------|-------------|
|
||||
| `rm_rf_root` | `rm -rf /` | Recursively deletes the entire filesystem root |
|
||||
| `rm_rf_wildcard` | `rm -rf /*` or `rm -rf *` | Deletes large portions of the filesystem |
|
||||
| `fork_bomb` | `:(){ :|:& };:` | Exhausts the process table, requiring a hard reboot |
|
||||
|
||||
### High
|
||||
|
||||
| Name | Pattern | Description |
|
||||
|------|---------|-------------|
|
||||
| `dd_if_device` | `dd if=` | Can overwrite disk devices or fill storage |
|
||||
| `mkfs` | `mkfs` | Formats a filesystem, permanently erasing all data |
|
||||
| `shred_device` | `shred /dev/` or `shred --remove` | Overwrites data in an unrecoverable way |
|
||||
|
||||
### Medium
|
||||
|
||||
| Name | Pattern | Description |
|
||||
|------|---------|-------------|
|
||||
| `chmod_777` | `chmod 777` | Grants world-readable/writable/executable permissions |
|
||||
| `sudo_rm` | `sudo rm` | Runs `rm` with elevated privileges |
|
||||
| `wget_pipe_sh` | `wget … \| sh` | Executes arbitrary remote code without inspection |
|
||||
| `curl_pipe_sh` | `curl … \| sh` | Executes arbitrary remote code without inspection |
|
||||
| `wget_pipe_bash` | `wget … \| bash` | Executes arbitrary remote code without inspection |
|
||||
| `curl_pipe_bash` | `curl … \| bash` | Executes arbitrary remote code without inspection |
|
||||
|
||||
### Low
|
||||
|
||||
| Name | Pattern | Description |
|
||||
|------|---------|-------------|
|
||||
| `git_push_force` | `git push --force` | Overwrites remote history, causing data loss for collaborators |
|
||||
| `chmod_recursive_permissive` | `chmod -R 6xx/7xx` | Exposes sensitive files to unintended access |
|
||||
|
||||
---
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Basic detection
|
||||
|
||||
```python
|
||||
from cleveragents.tui.shell_safety.pattern_detector import DangerousPatternDetector
|
||||
|
||||
detector = DangerousPatternDetector()
|
||||
|
||||
# Check a command
|
||||
warnings = detector.check("rm -rf /")
|
||||
if warnings:
|
||||
print(warnings[0].message)
|
||||
# [Critical] Dangerous command detected: rm -rf / recursively deletes ...
|
||||
|
||||
# Quick boolean check
|
||||
if detector.is_dangerous("sudo rm -rf /var/log"):
|
||||
print("Dangerous!")
|
||||
|
||||
# Highest danger level
|
||||
level = detector.max_danger_level("wget http://example.com/script.sh | bash")
|
||||
print(level) # ShellDangerLevel.MEDIUM
|
||||
```
|
||||
|
||||
### Custom patterns
|
||||
|
||||
```python
|
||||
from cleveragents.tui.shell_safety.danger_level import ShellDangerLevel
|
||||
from cleveragents.tui.shell_safety.dangerous_pattern import DangerousPattern
|
||||
from cleveragents.tui.shell_safety.pattern_detector import DangerousPatternDetector
|
||||
|
||||
detector = DangerousPatternDetector()
|
||||
|
||||
# Add a custom pattern
|
||||
detector.add_pattern(
|
||||
DangerousPattern(
|
||||
name="drop_database",
|
||||
pattern=r"\bDROP\s+DATABASE\b",
|
||||
level=ShellDangerLevel.CRITICAL,
|
||||
description="DROP DATABASE permanently deletes the entire database.",
|
||||
case_sensitive=False,
|
||||
)
|
||||
)
|
||||
|
||||
# Remove a built-in pattern
|
||||
detector.remove_pattern("git_push_force")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Integration with Shell Mode
|
||||
|
||||
The `DangerousPatternDetector` is invoked by the TUI shell mode handler
|
||||
(`cleveragents.tui.input.shell_exec`) before any subprocess is started.
|
||||
If `check_first()` returns a warning, the TUI presents a confirmation
|
||||
overlay before proceeding.
|
||||
|
||||
The shell mode is entered by typing `!` at the prompt. See
|
||||
[Shell Mode](tui.md#shell-mode) in the TUI reference for the full
|
||||
shell mode documentation.
|
||||
|
||||
---
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [TUI Reference](tui.md) — overall TUI architecture and shell mode
|
||||
- [TUI Permissions Screen](tui_permissions.md) — full-screen permission overlay
|
||||
- [TUI Permission Question Widget](tui_permission_question.md) — inline permission widget
|
||||
Reference in New Issue
Block a user