Secure Docker sandbox container pool for Hermes AI agent — zero-latency sandbox execution
Project description
Hermes Sandbox Setup
Secure Docker sandbox container pool for the Hermes AI agent. Zero-latency sandbox execution with full OWASP Top 10 security coverage.
⚠️ PyPI Publication: This package is ready for PyPI. Pending token setup. See PyPI placeholder section below.
What It Does
Hermes runs directly on the host with full terminal + file access. This means any prompt injection (from web scraping, file reads, or API responses) can execute rm -rf / on your production VM.
This repo provides execution-layer caging — a warm container pool that routes dangerous operations into isolated Docker containers at near-zero latency.
Architecture
┌───────────────────────────────────────┐
│ Hermes Agent (host) │
│ - reads your project files │
│ - thinks about the problem │
│ - decides: "I need to run a command" │
├───────────────────────────────────────┤
│ Sandbox Pool (3 warm containers) │
│ ┌───┐ ┌───┐ ┌───┐ │
│ │ 1 │ │ 2 │ │ 3 │ → docker exec │
│ └───┘ └───┘ └───┘ (~5ms-160ms) │
│ │
│ If pool empty → cold start │
│ docker run --rm (~500-800ms) │
└───────────────────────────────────────┘
Quick Start
1. Prerequisites
# Docker installed and user in docker group
docker --version
groups | grep docker
# If not: sudo usermod -aG docker $USER && newgrp docker
2. One-Command Setup
bash ~/.hermes/scripts/sandbox-setup.sh --pool-size 3 --memory 128m
Or with all options:
bash ~/.hermes/scripts/sandbox-setup.sh \
--pool-size 3 \ # warm containers (default: 3)
--memory 128m \ # max RAM per container (default: 128m)
--memory-reserve 16m \ # soft reservation (default: 16m)
--cpus 1 \ # CPU cores per container (default: 1)
--network none \ # network mode (default: none — for K8s use "host")
--read-only true \ # rootfs read-only (default: true)
--image debian:bookworm-slim \ # container image
--no-systemd \ # skip systemd service install
3. Run Commands in Sandbox
# Via warm pool (~5-160ms)
~/.hermes/scripts/sandbox-daemon.sh exec "kubectl get pods"
~/.hermes/scripts/sandbox-daemon.sh exec "curl https://untrusted-site.com"
# Via cold start (~500-800ms, auto fallback)
~/.hermes/scripts/sandbox-exec.sh "pip install some-package"
# Check pool status
~/.hermes/scripts/sandbox-daemon.sh status
4. Systemd Auto-Start
systemctl --user enable --now hermes-sandbox-pool
Security Coverage
OWASP Top 10 (Web) — 9/10 passed
| Category | Status | Implementation |
|---|---|---|
| A01 Access Control | ✅ | os.path.realpath() path allowlist, restricted Hermes profiles |
| A02 Crypto | ✅ | Only MD5 for cache keying (not security-critical) |
| A03 Injection | ✅ | 16KB command limit + container isolation |
| A04 Design | ✅ | Zero-Trust WAF documented, defense in depth |
| A05 Misconfig | ✅ | Resolved absolute paths in config, visudo validation |
| A06 Components | ✅ | Zero external deps, Debian stable |
| A07 Auth | ✅ | nginx deny-by-default, listen [IP_ADDRESS]:80 |
| A08 Integrity | ✅ | Hardcoded binary, .deb from git, no `curl |
| A09 Logging | ✅ | Full audit trail with duration, RC, output lines |
| A10 SSRF | ✅ | --network none in pool, no HTTP proxies |
OWASP Top 10 (LLM) — 1 critical finding fixed
LLM06 — Excessive Agency: Before the audit, Hermes had one profile with full host access. Now:
| Profile | Toolsets | Use Case |
|---|---|---|
default |
Full access (terminal, file, all) | Trusted tasks, known prompts |
research |
web, browser, vision, skills, search, memory, cronjob |
Web scraping, reading — no RCE risk |
sandbox |
All tools + verbose DEBUG logging | Debugging, untrusted terminal |
hermes --profile research # Read-only session
hermes --profile sandbox # Debug session with audit
See:
SECURITY_AUDIT.md— Full OWASP Web Top 10 reportSECURITY_AUDIT_LLM.md— Full OWASP LLM Top 10 reportLLM06_FIX.md— Restricted profiles documentationFINAL_REPORT.md— Post-audit summary with all metrics
Performance
| Metric | Value |
|---|---|
Cold docker run --rm |
~521ms |
| Warm pool exec (avg) | ~162-455ms |
| Pool idle RAM per container | ~380-432 KB |
| Pool idle RAM total (3 containers) | ~1.5 MB |
| Audit log entry size | ~100 bytes |
| Per-container memory limit | 128m (configurable) |
| Per-container CPU limit | 1 vCPU |
Recovery from failures
| Failure | Behavior |
|---|---|
| All containers busy | ✅ Transparent cold-start fallback (~600ms) |
| Docker daemon restart | ✅ CID files vanish → cold-start kicks in |
| Container killed externally | ✅ Next call selects different container |
| Parallel execs on same CID | ✅ Atomic mv claim — no race condition |
| Command > 16KB | ✅ Rejected safely (RC=64) |
Configuration
sandbox.yaml
pool:
size: 3
memory_limit: "128m"
memory_reserve: "16m"
cpus: "1"
network: "none"
read_only: true
image: "debian:bookworm-slim"
waf_mode: "read-only" # drops write/delete/exec at socket layer
allowed_file_paths:
- "/home/hermes"
tool_overrides: {}
Logging
# In ~/.hermes/config.yaml (per-profile)
logging:
level: DEBUG # DEBUG | INFO | WARNING | ERROR
backup_count: 3
max_size_mb: 5
file: sandbox-log.log # Optional: separate log file per profile
Audit log location: ~/.hermes/logs/sandbox-audit.log
2026-06-08T05:02:59Z POOL HIT=true rc=0 dur=123ms out=2L cid=89a93f85b752 cmd="echo test123 && cat /etc/hostname"
2026-06-08T05:07:08Z REJECTED 20000B cmd="AAAA..."
2026-06-08T05:10:38Z COLD DONE rc=0 dur=600ms out=1L
2026-06-08T05:25:21Z POOL-STOP containers=3
Go Rewrite — Effort Estimate
The sandbox scripts are not worth rewriting in Go. Here's why:
Current state: 7 KB of bash
sandbox-daemon.sh: 6,107 bytessandbox-exec.sh: 1,080 bytes
What Go would (not) buy
| Aspect | Bash | Go | Delta |
|---|---|---|---|
| Exec latency | ~162ms | ~160ms | 0% — Docker is the bottleneck |
| Startup | ~5ms | ~1ms | Negligible |
| Binary size | 7 KB | ~6 MB | 857x larger |
| Concurrency | Background & + mv atomic claim |
Native goroutines | Cleaner code, same result |
| Signal handling | trap (flaky with bg) |
os.Signal |
Better |
| Error handling | $? checks (easy to miss) |
Native error returns | Better |
| Cross-platform | Linux only | Linux/macOS/Windows | Better |
| Maintenance (you) | Simple bash — any sysadmin can edit | Needs Go toolchain | Worse |
| Distribution | Copy one .sh file |
Build for every arch | Worse |
| Auditability | Plain text, grep-friendly | Compiled binary — needs source | Worse |
Effort estimate
| Level | Hours | What you get |
|---|---|---|
| Minimal (CLI wrapper) | 4-6h | Same functionality, 300 lines Go, 6 MB binary |
| Full (with Docker SDK, health checks, structured logging) | 12-16h | Proper signal handling, JSON logging, channel-based pool |
| Complete (replacing all bash + Makefile + systemd integration) | 20-30h | A proper Go daemon with everything above |
Verdict
Not recommended for you. The scripts work. They're 7 KB, trivially editable, and the bottleneck is Docker (not bash). A Go rewrite would make maintenance harder for you (Go toolchain, compilation, cross-compilation, CI) for zero user-facing improvement.
Where Go IS worth it
If you want a rewrite, focus on ToolRecall's MCP daemon (the Python multiplexer). That's where concurrency, JSON-RPC routing, and long-running process management matter. The sandbox wrapper is fine as bash.
Files
├── usr/local/bin/
│ ├── sandbox-daemon.sh # Pool manager (start/stop/exec/status)
│ ├── sandbox-exec.sh # Single cold exec (legacy)
│ └── hermes-docker-enable # Hardcoded docker group wrapper
├── etc/
│ ├── sandbox.yaml # Pool configuration template
│ ├── sandbox-audit.log # Execution audit trail
│ └── sudoers.d/
│ └── hermes-docker # NOPASSWD rule for group enable
│ ├── systemd/system/
│ │ └── hermes-sandbox-pool.service
│ └── nginx/
│ └── nginx-toolrecall.conf # MCP proxy (deny-by-default)
├── debian-package/ # .deb build source
│ └── DEBIAN/
│ ├── control
│ └── postinst
├── SECURITY_AUDIT.md # OWASP Web Top 10
├── SECURITY_AUDIT_LLM.md # OWASP LLM Top 10
├── LLM06_FIX.md # Excessive Agency fix docs
├── FINAL_REPORT.md # Full metrics and post-audit summary
├── Makefile
└── README.md
PyPI
This package will be available on PyPI as:
pip install hermes-sandbox-setup
# or
uv pip install hermes-sandbox-setup
Then:
hermes-sandbox-setup --pool-size 3 --memory 128m
Status: PyPI token pending. Package structure is ready at
/home/hermes/hermes-sandbox-setup/. Rungit pushonce the remote is configured.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hermes_sandbox_setup-0.0.1.dev0.tar.gz.
File metadata
- Download URL: hermes_sandbox_setup-0.0.1.dev0.tar.gz
- Upload date:
- Size: 6.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9621998536503de2b4f0647987db233451cd5cf601534e471d03211b39fbaa78
|
|
| MD5 |
7e8372fb51dfeecf1957b71b494f8394
|
|
| BLAKE2b-256 |
b4b5fdc8f5501a5b551058a23fd0b252efcdc3f65845b2fb1b5491f71eababef
|
File details
Details for the file hermes_sandbox_setup-0.0.1.dev0-py3-none-any.whl.
File metadata
- Download URL: hermes_sandbox_setup-0.0.1.dev0-py3-none-any.whl
- Upload date:
- Size: 6.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.11.16 {"installer":{"name":"uv","version":"0.11.16","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Debian GNU/Linux","version":"12","id":"bookworm","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
709b895998c17cee965ff1f9c4491de82204234abb3ead7d37649d95969b4920
|
|
| MD5 |
b233e8d1ac3db3818be766118b9326ed
|
|
| BLAKE2b-256 |
d531c3c567498d2b503c95f5cc207c0a4c4997e9fbc72570ea857bb974e6b7b1
|