Update all remaining files that enumerate terminal backends to include Daytona. Covers security docs (bypass info, backend comparison table), environment variables reference (DAYTONA_API_KEY, TERMINAL_DAYTONA_IMAGE, container resources header), AGENTS.md (architecture tree, config keys), environments/README.md, hermes_base_env.py field description, and various module docstrings. Follow-up to PR #451 merge.
329 lines
12 KiB
Markdown
329 lines
12 KiB
Markdown
---
|
|
sidebar_position: 8
|
|
title: "Security"
|
|
description: "Security model, dangerous command approval, user authorization, container isolation, and production deployment best practices"
|
|
---
|
|
|
|
# Security
|
|
|
|
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 five layers:
|
|
|
|
1. **User authorization** — who can talk to the agent (allowlists, DM pairing)
|
|
2. **Dangerous command approval** — human-in-the-loop for destructive operations
|
|
3. **Container isolation** — Docker/Singularity/Modal sandboxing with hardened settings
|
|
4. **MCP credential filtering** — environment variable isolation for MCP subprocesses
|
|
5. **Context file scanning** — prompt injection detection in project files
|
|
|
|
## 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.
|
|
|
|
### 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` | World-writable permissions |
|
|
| `mkfs` | Format filesystem |
|
|
| `dd if=` | Disk copy |
|
|
| `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 |
|
|
| `curl ... \| sh` | Pipe remote content to shell |
|
|
| `bash -c`, `python -e` | Shell/script execution via flags |
|
|
| `find -exec rm`, `find -delete` | Find with destructive actions |
|
|
| Fork bomb patterns | Fork bombs |
|
|
|
|
:::info
|
|
**Container bypass**: When running in `docker`, `singularity`, `modal`, or `daytona` backends, dangerous command checks are **skipped** because the container itself is the security boundary. Destructive commands inside a container can't harm the host.
|
|
:::
|
|
|
|
### 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`:
|
|
|
|
```yaml
|
|
# Permanently allowed dangerous command patterns
|
|
command_allowlist:
|
|
- rm
|
|
- systemctl
|
|
```
|
|
|
|
These patterns are loaded at startup and silently approved in all future sessions.
|
|
|
|
:::tip
|
|
Use `hermes config edit` to review or remove patterns from your permanent allowlist.
|
|
:::
|
|
|
|
## 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:
|
|
|
|
1. **Per-platform allow-all flag** (e.g., `DISCORD_ALLOW_ALL_USERS=true`)
|
|
2. **DM pairing approved list** (users approved via pairing codes)
|
|
3. **Platform-specific allowlists** (e.g., `TELEGRAM_ALLOWED_USERS=12345,67890`)
|
|
4. **Global allowlist** (`GATEWAY_ALLOWED_USERS=12345,67890`)
|
|
5. **Global allow-all** (`GATEWAY_ALLOW_ALL_USERS=true`)
|
|
6. **Default: deny**
|
|
|
|
### Platform Allowlists
|
|
|
|
Set allowed user IDs as comma-separated values in `~/.hermes/.env`:
|
|
|
|
```bash
|
|
# 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
|
|
```
|
|
|
|
:::warning
|
|
If **no allowlists are configured** and `GATEWAY_ALLOW_ALL_USERS` is not set, **all users are denied**. The gateway logs a warning at startup:
|
|
|
|
```
|
|
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:**
|
|
|
|
1. An unknown user sends a DM to the bot
|
|
2. The bot replies with an 8-character pairing code
|
|
3. The bot owner runs `hermes pairing approve <platform> <code>` on the CLI
|
|
4. The user is permanently approved for that platform
|
|
|
|
**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:**
|
|
|
|
```bash
|
|
# 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`):
|
|
|
|
```python
|
|
_SECURITY_ARGS = [
|
|
"--cap-drop", "ALL", # Drop ALL Linux capabilities
|
|
"--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`:
|
|
|
|
```yaml
|
|
terminal:
|
|
backend: docker
|
|
docker_image: "nikolaik/python-nodejs:python3.11-nodejs20"
|
|
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 `/workspace` and `/root` from `~/.hermes/sandboxes/docker/<task_id>/`
|
|
- **Ephemeral mode** (`container_persistent: false`): Uses tmpfs for workspace — everything is lost on cleanup
|
|
|
|
:::tip
|
|
For production gateway deployments, use `docker`, `modal`, or `daytona` backend to isolate agent commands from your host system. This eliminates the need for dangerous command approval entirely.
|
|
:::
|
|
|
|
## 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 |
|
|
|
|
## MCP Credential Handling
|
|
|
|
MCP (Model Context Protocol) server subprocesses receive a **filtered environment** to prevent accidental credential leakage.
|
|
|
|
### Safe Environment Variables
|
|
|
|
Only these variables are passed through from the host to MCP stdio subprocesses:
|
|
|
|
```
|
|
PATH, HOME, USER, LANG, LC_ALL, TERM, SHELL, TMPDIR
|
|
```
|
|
|
|
Plus any `XDG_*` variables. All other environment variables (API keys, tokens, secrets) are **stripped**.
|
|
|
|
Variables explicitly defined in the MCP server's `env` config are passed through:
|
|
|
|
```yaml
|
|
mcp_servers:
|
|
github:
|
|
command: "npx"
|
|
args: ["-y", "@modelcontextprotocol/server-github"]
|
|
env:
|
|
GITHUB_PERSONAL_ACCESS_TOKEN: "ghp_..." # Only this is passed
|
|
```
|
|
|
|
### Credential Redaction
|
|
|
|
Error messages from MCP tools are sanitized before being returned to the LLM. The following patterns are replaced with `[REDACTED]`:
|
|
|
|
- GitHub PATs (`ghp_...`)
|
|
- OpenAI-style keys (`sk-...`)
|
|
- Bearer tokens
|
|
- `token=`, `key=`, `API_KEY=`, `password=`, `secret=` parameters
|
|
|
|
### Context File Injection Protection
|
|
|
|
Context files (AGENTS.md, .cursorrules, SOUL.md) are scanned for prompt injection before being included in the system prompt. The scanner checks for:
|
|
|
|
- Instructions to ignore/disregard prior instructions
|
|
- Hidden HTML comments with suspicious keywords
|
|
- Attempts to read secrets (`.env`, `credentials`, `.netrc`)
|
|
- Credential exfiltration via `curl`
|
|
- Invisible Unicode characters (zero-width spaces, bidirectional overrides)
|
|
|
|
Blocked files show a warning:
|
|
|
|
```
|
|
[BLOCKED: AGENTS.md contained potential prompt injection (prompt_injection). Content not loaded.]
|
|
```
|
|
|
|
## Best Practices for Production Deployment
|
|
|
|
### Gateway Deployment Checklist
|
|
|
|
1. **Set explicit allowlists** — never use `GATEWAY_ALLOW_ALL_USERS=true` in production
|
|
2. **Use container backend** — set `terminal.backend: docker` in config.yaml
|
|
3. **Restrict resource limits** — set appropriate CPU, memory, and disk limits
|
|
4. **Store secrets securely** — keep API keys in `~/.hermes/.env` with proper file permissions
|
|
5. **Enable DM pairing** — use pairing codes instead of hardcoding user IDs when possible
|
|
6. **Review command allowlist** — periodically audit `command_allowlist` in config.yaml
|
|
7. **Set `MESSAGING_CWD`** — don't let the agent operate from sensitive directories
|
|
8. **Run as non-root** — never run the gateway as root
|
|
9. **Monitor logs** — check `~/.hermes/logs/` for unauthorized access attempts
|
|
10. **Keep updated** — run `hermes update` regularly for security patches
|
|
|
|
### Securing API Keys
|
|
|
|
```bash
|
|
# Set proper permissions on the .env file
|
|
chmod 600 ~/.hermes/.env
|
|
|
|
# Keep separate keys for different services
|
|
# Never commit .env files to version control
|
|
```
|
|
|
|
### Network Isolation
|
|
|
|
For maximum security, run the gateway on a separate machine or VM:
|
|
|
|
```yaml
|
|
terminal:
|
|
backend: ssh
|
|
ssh_host: "agent-worker.local"
|
|
ssh_user: "hermes"
|
|
ssh_key: "~/.ssh/hermes_agent_key"
|
|
```
|
|
|
|
This keeps the gateway's messaging connections separate from the agent's command execution.
|