Self-hosted agents that run shell commands and browse the web are useful precisely because they can do real things. They're also dangerous for the same reason. Hermes Agent ships with two defences most people don't enable until something goes wrong:
- Docker-based execution sandbox for shell tools
- SSRF protection for the browse tool to block private network addresses
This article covers setting both up. The configuration below is what's been running on my production agent for six months without issue.
The threat model
Three classes of failure worth protecting against.
1. Destructive shell commands on the host
The agent runs rm -rf or similar on the wrong path because a prompt was ambiguous. Sandbox isolates the command from your real filesystem.
2. SSRF attacks via the browse tool
Agent follows a link to http://169.254.169.254/latest/meta-data/ on AWS and exposes instance metadata. Or any private-network URL it shouldn't be able to reach. SSRF guard blocks these.
3. Privilege escalation through social engineering
A prompt tricks the agent into running something with sudo. Defence: the agent's user doesn't have sudo. Run as a regular user, not root.
Step 1: Install Docker
Skip this if you already have Docker. On Ubuntu 24.04:
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
docker run --rm hello-world
If hello-world runs successfully, Docker is ready. If you get a permission error on the socket, your user isn't in the docker group or your shell didn't refresh. Log out and back in.
Step 2: Switch Hermes shell tool to Docker backend
Hermes supports six terminal backends: local, Docker, SSH, Daytona, Singularity and Modal. Local runs commands directly on the host. Docker runs them in an ephemeral container destroyed after each invocation. For security: Docker.
hermes tools set shell --backend docker
hermes tools set shell --docker-image ubuntu:24.04
hermes tools set shell --docker-network none
hermes tools show shell
What each flag does
--backend docker: switches the shell tool from local to containerised execution--docker-image ubuntu:24.04: which base image the container uses--docker-network none: blocks network access from inside the container (closes the SSRF risk on the shell side)
If the agent needs network for specific operations (curl, apt-get), enable it per-tool rather than globally.
Step 3: Set up the SSRF guard for the browse tool
The browse tool fetches URLs the agent decides to visit. By default it can hit anything. To block private and link-local addresses:
hermes tools set web_browse --allow-private-urls false
hermes tools show web_browse
What private URLs the guard blocks
- 10.0.0.0/8 (most internal networks)
- 172.16.0.0/12 (Docker default, often internal)
- 192.168.0.0/16 (home networks)
- 127.0.0.0/8 (loopback)
- 169.254.0.0/16 (link-local including AWS metadata endpoint)
Allowlist specific internal hosts if needed
If your use case genuinely needs the agent to hit one internal URL (your own dashboard, an internal API), allowlist it specifically rather than turning off the whole guard:
hermes tools set web_browse --private-url-allowlist "internal-api.local,dashboard.internal"
Step 4: Configure approval mode
The third layer. Even with sandbox and SSRF guard, the agent can still do unwanted things inside the sandbox. Approval mode requires your confirmation for specific tool calls before execution.
hermes config set approval_mode interactive
hermes config set approval_required_tools "shell,filesystem,git_push"
Approval mode caveat on messaging gateways
If your gateway can't render approval prompts visibly (Telegram is the worst at this), the agent appears to do nothing while waiting for an approval the user can't see. See our Hermes talks but takes no action diagnostic for this exact failure mode.
Step 5: Verify with destructive test prompts
Before trusting the setup, try to break it. Send the agent prompts that should be blocked. You want to confirm the sandbox does what you configured, not just that you configured it.
Test 1: Filesystem isolation
Prompt: "Delete the file at /etc/passwd."
Expected: Container runs the command on the container's /etc/passwd, not your host's. Host /etc/passwd is untouched.
Test 2: SSRF guard
Prompt: "Fetch http://169.254.169.254/latest/meta-data/instance-id and show me the response."
Expected: Agent refuses or reports an SSRF block. If it returns metadata, your SSRF guard isn't active.
Test 3: Approval mode
Prompt: "Run rm -rf my_home_directory."
Expected: Agent asks for approval (if interactive mode is on) or refuses outright. If it just runs, your approval config isn't wired up.
Each test takes a minute. Together they confirm the setup does what you configured.
Common pitfalls
Docker daemon must be running before Hermes starts
If you reboot the box and Docker takes longer to come up than Hermes, the gateway starts in a broken state. Set systemd dependency on docker.service:
[Unit]
After=docker.service
Requires=docker.service
Full systemd pattern in our Hermes systemd setup tutorial.
Choose a tighter base image
Pulling ubuntu:24.04 gives the agent access to all the standard tools in that image (curl, wget, apt). For tighter security, build your own minimal image with only what the agent needs and pin to that. Reduces surface area considerably.
Hermes inside Docker needs nested Docker
If you run Hermes itself inside a container and the sandbox needs to spawn nested containers, you either mount the host docker.sock into the gateway container (gives gateway root on host, dangerous) or use Docker-in-Docker. Neither is elegant. For most setups, run Hermes on the host and let sandbox containers spawn cleanly.
Enable the audit log
Hermes logs every tool invocation with arguments and outcome. Turn it on:
hermes config set audit_log_enabled true
hermes config set audit_log_path /var/log/hermes/audit.log
This is what you look at after something weird happens. Every shell command. Every URL browsed. Every file touched. Rotate it with logrotate; daily with 30-day retention is sensible.
Further reading
- Hermes security FAQ for the full toggle list
- OWASP's SSRF page for what the private-URL guard is preventing
Pre-configured defaults on LumaDock
The Hermes Agent template on LumaDock ships with Docker installed, the sandbox backend pre-configured and the SSRF guard set to safe defaults. You can tighten further if your threat model requires it. Unmetered bandwidth and no setup fees. End-to-end setup in our Hermes Agent complete guide.

