How the Dangerous Command Approval System Works in Hermes Agent
Hermes Agent’s dangerous command approval system intercepts destructive shell commands in the terminal tool using regex pattern matching, maintains thread-safe session and permanent allow-lists, and prompts users for interactive or asynchronous approval before execution.
The Hermes Agent repository implements a robust safety layer to prevent accidental data loss when executing shell commands through its terminal tool. This dangerous command approval system analyzes every command against predefined destructive patterns, manages user consent through multiple approval pathways, and persists safety preferences across sessions. Understanding this mechanism is essential for developers integrating Hermes Agent into automated workflows or extending its tool registry.
Architecture of the Dangerous Command Approval System
The system centers on two primary modules: tools/approval.py serves as the single source of truth for dangerous command detection and state management, while tools/terminal_tool.py acts as the execution gateway that invokes these safety checks.
Core Components and Data Structures
The tools/approval.py module defines DANGEROUS_PATTERNS, a list of tuples containing compiled regex patterns and human-readable descriptions. These patterns detect operations like recursive deletion, filesystem formatting, or permission changes on sensitive paths.
State management relies on three thread-safe data structures:
_session_approved: Tracks patterns approved for the current process lifetime_permanent_approved: Maintains patterns approved indefinitely_pending: Queues approval requests for asynchronous gateway processing
The tools/terminal_tool.py module implements _check_dangerous_command(), a thin wrapper that calls approval.check_dangerous_command() before executing any shell command.
Command Detection Flow in terminal_tool.py
When a command arrives at the terminal tool, the system executes a multi-stage validation pipeline to determine if user intervention is required.
Pattern Matching Against Dangerous Commands
The detect_dangerous_command() function in tools/approval.py iterates through DANGEROUS_PATTERNS, testing each regex against the normalized command string. When a match occurs, the function returns a tuple containing a boolean flag, the pattern key, and the description.
from tools.approval import detect_dangerous_command
is_dangerous, pattern_key, description = detect_dangerous_command("rm -rf /home/user/data")
# Returns: (True, "rm", "recursive deletion")
Environment-Based Bypasses for Containerized Execution
Container-based backends receive automatic approval because they execute in isolated environments. The tools/terminal_tool.py module checks the env_type parameter and bypasses dangerous command detection for docker, singularity, modal, and daytona environments.
if env_type in ("docker", "singularity", "modal", "daytona"):
return {"approved": True, "message": None}
Session State Verification
For local execution environments, the system checks the HERMES_SESSION_KEY against _session_approved. If the user previously approved this pattern during the current session, execution proceeds without prompting.
if is_approved(session_key, pattern_key):
return {"approved": True, "message": None}
Approval Pathways and User Interaction
When a dangerous command is detected and not pre-approved, Hermes Agent supports three distinct pathways for obtaining user consent.
Interactive CLI Prompting
In command-line interface mode, prompt_dangerous_approval() renders an ASCII warning displaying the command, the risk description, and four options: [o]nce, [s]ession, [a]lways, or [d]eny. The function uses prompt_toolkit for robust input handling.
Selecting session adds the pattern to _session_approved, while always triggers approve_permanent() and persists the choice to ~/.hermes/config.yaml under the command_allowlist key.
Asynchronous Gateway Approval
For messenger or gateway integrations (Telegram, Discord, etc.), the system uses environment variables HERMES_GATEWAY_SESSION or HERMES_EXEC_ASK to detect async contexts. Instead of blocking for input, check_dangerous_command() calls submit_pending() to queue the request and returns a payload with "status": "approval_required".
The gateway retrieves the pending request using pop_pending() when the user responds, allowing the command to proceed or abort based on the remote approval decision.
Force Execution Mode
After obtaining user consent through any pathway, subsequent calls to terminal_tool() can pass force=True to bypass the approval check entirely. This flag indicates that the user has already explicitly approved this specific execution context.
Permanent Allow-List Persistence
The dangerous command approval system maintains continuity across application restarts through a persistent configuration store.
Configuring command_allowlist in config.yaml
When a user selects the always option during approval, the system invokes approve_permanent(pattern_key), which adds the pattern to an in-memory set and triggers save_permanent_allowlist(). This function writes to ~/.hermes/config.yaml, appending the pattern key to the command_allowlist list.
from tools.approval import approve_permanent, save_permanent_allowlist, load_permanent_allowlist
# Permanently approve the 'rm' pattern
approve_permanent('rm')
save_permanent_allowlist(load_permanent_allowlist() | {'rm'})
State Hydration on Startup
During initialization, tools/approval.py calls load_permanent_allowlist(), which reads ~/.hermes/config.yaml and populates _permanent_approved. This ensures that previously approved dangerous command patterns remain authorized across system restarts without requiring re-prompting.
Practical Implementation Examples
Manually Invoking the Approval Check
For testing or custom tool development, you can directly call the approval detection logic:
from tools.approval import check_dangerous_command
info = check_dangerous_command(
command="chmod 777 -R /",
env_type="local",
)
print(info)
# Output: {'approved': False, 'message': '...', 'status': 'approval_required', ...}
Bypassing Approval After Explicit Consent
When building automation that has already obtained user approval, use the force flag to skip redundant checks:
# First call - triggers approval prompt
res1 = terminal_tool(command="rm -rf /tmp/old_data")
# After user approves "once", second call with force=True
res2 = terminal_tool(command="rm -rf /tmp/old_data", force=True)
Adding Patterns to Permanent Allow-List Programmatically
For enterprise deployments or automated setup scripts:
from tools.approval import approve_permanent, save_permanent_allowlist, load_permanent_allowlist
# Approve specific pattern permanently
approve_permanent('chmod_recursive')
save_permanent_allowlist(load_permanent_allowlist() | {'chmod_recursive'})
Key Source Files
| File | Description | Key Functions |
|---|---|---|
tools/approval.py |
Central authority for dangerous command detection and approval state management | detect_dangerous_command(), check_dangerous_command(), approve_permanent(), save_permanent_allowlist(), load_permanent_allowlist(), submit_pending(), pop_pending() |
tools/terminal_tool.py |
Terminal tool implementation that invokes approval checks before execution | _check_dangerous_command(), terminal_tool() |
hermes_cli/config.py |
Configuration management for persistent allow-lists | Handles command_allowlist in ~/.hermes/config.yaml |
tools/registry.py |
Tool schema registration (exposes terminal tool to LLM) | Terminal tool registration |
Summary
- Centralized Detection: The
tools/approval.pymodule maintainsDANGEROUS_PATTERNSand providesdetect_dangerous_command()to identify destructive operations before execution. - Multi-Layered Approval: The system supports interactive CLI prompts, asynchronous gateway approvals for messaging platforms, and automatic bypasses for containerized environments.
- Persistent State Management: User preferences for "session" or "always" approvals are tracked in thread-safe sets and persisted to
~/.hermes/config.yamlviasave_permanent_allowlist(). - Integration Point:
tools/terminal_tool.pycalls_check_dangerous_command()before executing any shell command, ensuring the dangerous command approval system gates all local execution.
Frequently Asked Questions
How does Hermes Agent detect dangerous commands in the terminal tool?
Hermes Agent uses regex pattern matching defined in tools/approval.py. The detect_dangerous_command() function iterates through DANGEROUS_PATTERNS, testing each compiled regex against the command string. When a match occurs, the system identifies the specific risk (such as recursive deletion or permission changes) and triggers the approval workflow before allowing tools/terminal_tool.py to execute the command.
What happens when a dangerous command is detected in a Docker or container environment?
Containerized environments including docker, singularity, modal, and daytona bypass the dangerous command approval system entirely. In tools/terminal_tool.py, the _check_dangerous_command() function checks the env_type parameter and returns immediate approval for these isolated environments, as the container boundary provides sufficient protection against system-wide damage.
How does the permanent allow-list work across application restarts?
When a user selects the "always" option during approval, the system calls approve_permanent() in tools/approval.py, which adds the pattern key to an in-memory set and triggers save_permanent_allowlist(). This function writes the pattern to the command_allowlist key in ~/.hermes/config.yaml. On startup, load_permanent_allowlist() reads this configuration file and rehydrates the _permanent_approved set, ensuring approved patterns persist across restarts without requiring re-prompting.
Can the dangerous command approval system be bypassed programmatically?
Yes, after obtaining explicit user consent, developers can pass force=True to the terminal_tool() function in tools/terminal_tool.py to skip the approval check entirely. Additionally, the check_dangerous_command() function in tools/approval.py can be imported and called directly for custom validation logic, or specific patterns can be added to the permanent allow-list programmatically using approve_permanent() and save_permanent_allowlist().
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →