--- sidebar_position: 8 title: "Security" description: "Security model, dangerous command approval, user authorization, container isolation, and production deployment best practices"
安全设置
Hermes Agent is designed with a defense-in-depth security model. This page covers every security boundary — from command approval to container isolation to user authorization on messaging platforms.
Overview
The security model has seven layers:
- User authorization — who can talk to the agent (allowlists, DM pairing)
- Dangerous command approval — human-in-the-loop for destructive operations
- Container isolation — Docker/Singularity/Modal sandboxing with hardened settings
- MCP credential filtering — environment variable isolation for MCP subprocesses
- Context file scanning — prompt injection detection in project files
- Cross-session isolation — sessions cannot access each other's data or state; cron job storage paths are hardened against path traversal attacks
- Input sanitization — working directory parameters in terminal tool backends are validated against an allowlist to prevent shell injection
Dangerous Command Approval
Before executing any command, Hermes checks it against a curated list of dangerous patterns. If a match is found, the user must explicitly approve it.
Approval Modes
The approval system supports three modes, configured via approvals.mode in ~/.hermes/config.yaml:
approvals:
mode: manual # manual | smart | off
timeout: 60 # seconds to wait for user response (default: 60)
| Mode | Behavior |
|---|---|
| manual (default) | Always prompt the user for approval on dangerous commands |
| smart | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., python -c "print('hello')") are auto-approved. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |
| off | Disable all approval checks — equivalent to running with --yolo. All commands execute without prompts. |
YOLO Mode
YOLO mode bypasses all dangerous command approval prompts for the current session. It can be activated three ways:
- CLI flag: Start a session with
hermes --yoloorhermes chat --yolo - Slash command: Type
/yoloduring a session to toggle it on/off - Environment variable: Set
HERMES_YOLO_MODE=1
The /yolo command is a toggle — each use flips the mode on or off:
> /yolo
⚡ YOLO mode ON — all commands auto-approved. Use with caution.
> /yolo
⚠ YOLO mode OFF — dangerous commands will require approval.
YOLO mode is available in both CLI and gateway sessions. Internally, it sets the HERMES_YOLO_MODE environment variable which is checked before every command execution.
:::danger YOLO mode disables all dangerous command safety checks for the session. Use only when you fully trust the commands being generated (e.g., well-tested automation scripts in disposable environments).
Approval Timeout
When a dangerous command prompt appears, the user has a configurable amount of time to respond. If no response is given within the timeout, the command is denied by default (fail-closed).
Configure the timeout in ~/.hermes/config.yaml:
approvals:
timeout: 60 # seconds (default: 60)
What Triggers Approval
The following patterns trigger approval prompts (defined in tools/approval.py):
| Pattern | Description |
|---|---|
rm -r / rm --recursive |
Recursive delete |
rm ... / |
Delete in root path |
chmod 777/666 / o+w / a+w |
World/other-writable permissions |
chmod --recursive with unsafe perms |
Recursive world/other-writable (long flag) |
chown -R root / chown --recursive root |
Recursive chown to root |
mkfs |
Format filesystem |
dd if= |
Disk copy |
> /dev/sd |
Write to block device |
DROP TABLE/DATABASE |
SQL DROP |
DELETE FROM (without WHERE) |
SQL DELETE without WHERE |
TRUNCATE TABLE |
SQL TRUNCATE |
> /etc/ |
Overwrite system config |
systemctl stop/disable/mask |
Stop/disable system services |
kill -9 -1 |
Kill all processes |
pkill -9 |
Force kill processes |
| Fork bomb patterns | Fork bombs |
bash -c / sh -c / zsh -c / ksh -c |
Shell command execution via -c flag (including combined flags like -lc) |
python -e / perl -e / ruby -e / node -c |
Script execution via -e/-c flag |
curl ... \| sh / wget ... \| sh |
Pipe remote content to shell |
bash <(curl ...) / sh <(wget ...) |
Execute remote script via process substitution |
tee to /etc/, ~/.ssh/, ~/.hermes/.env |
Overwrite sensitive file via tee |
> / >> to /etc/, ~/.ssh/, ~/.hermes/.env |
Overwrite sensitive file via redirection |
xargs rm |
xargs with rm |
find -exec rm / find -delete |
Find with destructive actions |
cp/mv/install to /etc/ |
Copy/move file into system config |
sed -i / sed --in-place on /etc/ |
In-place edit of system config |
pkill/killall hermes/gateway |
Self-termination prevention |
gateway run with &/disown/nohup/setsid |
Prevents starting gateway outside service manager |
Approval Flow (CLI)
In the interactive CLI, dangerous commands show an inline approval prompt:
⚠️ DANGEROUS COMMAND: recursive delete
rm -rf /tmp/old-project
[o]nce | [s]ession | [a]lways | [d]eny
Choice [o/s/a/D]:
The four options:
- once — allow this single execution
- session — allow this pattern for the rest of the session
- always — add to permanent allowlist (saved to
config.yaml) - deny (default) — block the command
Approval Flow (Gateway/Messaging)
On messaging platforms, the agent sends the dangerous command details to the chat and waits for the user to reply:
- Reply yes, y, approve, ok, or go to approve
- Reply no, n, deny, or cancel to deny
The HERMES_EXEC_ASK=1 environment variable is automatically set when running the gateway.
Permanent Allowlist
Commands approved with "always" are saved to ~/.hermes/config.yaml:
# Permanently allowed dangerous command patterns
command_allowlist:
- rm
- systemctl
These patterns are loaded at startup and silently approved in all future sessions.
User Authorization (Gateway)
When running the messaging gateway, Hermes controls who can interact with the bot through a layered authorization system.
Authorization Check Order
The _is_user_authorized() method checks in this order:
- Per-platform allow-all flag (e.g.,
DISCORD_ALLOW_ALL_USERS=true) - DM pairing approved list (users approved via pairing codes)
- Platform-specific allowlists (e.g.,
TELEGRAM_ALLOWED_USERS=12345,67890) - Global allowlist (
GATEWAY_ALLOWED_USERS=12345,67890) - Global allow-all (
GATEWAY_ALLOW_ALL_USERS=true) - Default: deny
Platform Allowlists
Set allowed user IDs as comma-separated values in ~/.hermes/.env:
# Platform-specific allowlists
TELEGRAM_ALLOWED_USERS=123456789,987654321
DISCORD_ALLOWED_USERS=111222333444555666
WHATSAPP_ALLOWED_USERS=15551234567
SLACK_ALLOWED_USERS=U01ABC123
# Cross-platform allowlist (checked for all platforms)
GATEWAY_ALLOWED_USERS=123456789
# Per-platform allow-all (use with caution)
DISCORD_ALLOW_ALL_USERS=true
# Global allow-all (use with extreme caution)
GATEWAY_ALLOW_ALL_USERS=true
No user allowlists configured. All unauthorized users will be denied.
Set GATEWAY_ALLOW_ALL_USERS=true in ~/.hermes/.env to allow open access,
or configure platform allowlists (e.g., TELEGRAM_ALLOWED_USERS=your_id).
DM Pairing System
For more flexible authorization, Hermes includes a code-based pairing system. Instead of requiring user IDs upfront, unknown users receive a one-time pairing code that the bot owner approves via the CLI.
How it works:
- An unknown user sends a DM to the bot
- The bot replies with an 8-character pairing code
- The bot owner runs
hermes pairing approve <platform> <code>on the CLI - The user is permanently approved for that platform
Control how unauthorized direct messages are handled in ~/.hermes/config.yaml:
unauthorized_dm_behavior: pair
whatsapp:
unauthorized_dm_behavior: ignore
pairis the default. Unauthorized DMs get a pairing code reply.ignoresilently drops unauthorized DMs.- Platform sections override the global default, so you can keep pairing on Telegram while keeping WhatsApp silent.
Security features (based on OWASP + NIST SP 800-63-4 guidance):
| Feature | Details |
|---|---|
| Code format | 8-char from 32-char unambiguous alphabet (no 0/O/1/I) |
| Randomness | Cryptographic (secrets.choice()) |
| Code TTL | 1 hour expiry |
| Rate limiting | 1 request per user per 10 minutes |
| Pending limit | Max 3 pending codes per platform |
| Lockout | 5 failed approval attempts → 1-hour lockout |
| File security | chmod 0600 on all pairing data files |
| Logging | Codes are never logged to stdout |
Pairing CLI commands:
# List pending and approved users
hermes pairing list
# Approve a pairing code
hermes pairing approve telegram ABC12DEF
# Revoke a user's access
hermes pairing revoke telegram 123456789
# Clear all pending codes
hermes pairing clear-pending
Storage: Pairing data is stored in ~/.hermes/pairing/ with per-platform JSON files:
- {platform}-pending.json — pending pairing requests
- {platform}-approved.json — approved users
- _rate_limits.json — rate limit and lockout tracking
Container Isolation
When using the docker terminal backend, Hermes applies strict security hardening to every container.
Docker Security Flags
Every container runs with these flags (defined in tools/environments/docker.py):
_SECURITY_ARGS = [
"--cap-drop", "ALL", # Drop ALL Linux capabilities
"--cap-add", "DAC_OVERRIDE", # Root can write to bind-mounted dirs
"--cap-add", "CHOWN", # Package managers need file ownership
"--cap-add", "FOWNER", # Package managers need file ownership
"--security-opt", "no-new-privileges", # Block privilege escalation
"--pids-limit", "256", # Limit process count
"--tmpfs", "/tmp:rw,nosuid,size=512m", # Size-limited /tmp
"--tmpfs", "/var/tmp:rw,noexec,nosuid,size=256m", # No-exec /var/tmp
"--tmpfs", "/run:rw,noexec,nosuid,size=64m", # No-exec /run
]
Resource Limits
Container resources are configurable in ~/.hermes/config.yaml:
terminal:
backend: docker
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
docker_forward_env: [] # Explicit allowlist only; empty keeps secrets out of the container
container_cpu: 1 # CPU cores
container_memory: 5120 # MB (default 5GB)
container_disk: 51200 # MB (default 50GB, requires overlay2 on XFS)
container_persistent: true # Persist filesystem across sessions
Filesystem Persistence
- Persistent mode (
container_persistent: true): Bind-mounts/workspaceand/rootfrom~/.hermes/sandboxes/docker/<task_id>/ - Ephemeral mode (
container_persistent: false): Uses tmpfs for workspace — everything is lost on cleanup
Terminal Backend Security Comparison
| Backend | Isolation | Dangerous Cmd Check | Best For |
|---|---|---|---|
| local | None — runs on host | ✅ Yes | Development, trusted users |
| ssh | Remote machine | ✅ Yes | Running on a separate server |
| docker | Container | ❌ Skipped (container is boundary) | Production gateway |
| singularity | Container | ❌ Skipped | HPC environments |
| modal | Cloud sandbox | ❌ Skipped | Scalable cloud isolation |
| daytona | Cloud sandbox | ❌ Skipped | Persistent cloud workspaces |
Environment Variable Passthrough {#environment-variable-passthrough}
Both execute_code and terminal strip sensitive environment variables from child processes to prevent credential exfiltration by LLM-generated code. However, skills that declare required_environment_variables legitimately need access to those vars.
How It Works
Two mechanisms allow specific variables through the sandbox filters:
1. Skill-scoped passthrough (automatic)
When a skill is loaded (via skill_view or the /skill command) and declares required_environment_variables, any of those vars that are actually set in the environment are automatically registered as passthrough. Missing vars (still in setup-needed state) are not registered.
# In a skill's SKILL.md frontmatter
required_environment_variables:
- name: TENOR_API_KEY
prompt: Tenor API key
help: Get a key from https://developers.google.com/tenor
After loading this skill, TENOR_API_KEY passes through to execute_code, terminal (local), and remote backends (Docker, Modal) — no manual configuration needed.
:::info Docker & Modal
Prior to v0.5.1, Docker's forward_env was a separate system from the skill passthrough. They are now merged — skill-declared env vars are automatically forwarded into Docker containers and Modal sandboxes without needing to add them to docker_forward_env manually.