25 Oct 2026

Hermes Part 1: Sandbox setup and tests

Stacked shipping containers, lit orange by low sun, against a deep blue sky

In brief

This three-part series shows how we used Hermes, an autonomous agent, to set up a recurring security job whose scripts read text from the internet, with controls to reduce the risk of that text taking over the agent. Our introduction to Hermes explains how it is built and where the main risks lie. This article, Part 1, covers the demo environment: a container with a read-only rules folder, approval for changes to the agent’s memory and skills, no secrets in the sandbox, and a network allowlist backed by switching off the tools that bypass it. We tested each one, and one test found a way around the allowlist, which we explain.

Setting up the environment

Hermes’s guide to running it on a work machine warns that its deny rules guard against an honest-but-mistaken agent. They are not a sandbox against a deliberately adversarial one. For real containment, the guide recommends an isolated terminal backend.

We built this demo with that in mind in late October 2026, using Hermes Agent 0.22.4 and Docker Desktop 4.96.3 on a MacBook Pro (Apple M5 Pro). It runs in a dedicated Hermes profile with four restrictions: its commands execute in a container, its rules folder is read-only so it cannot change its own tools, it holds no secrets, and its commands are set to send network traffic through an allowlist proxy. A separate builder profile drafted the scripts and tests. A person copied them across, and the hardened profile only runs them.

The demo shows how to put security controls around Hermes so it can run a recurring job safely. It is not production-ready. Using this pattern in production would need further hardening.

Run commands in a container with a read-only rules folder

The core control is Hermes’s Docker terminal backend, with two folders mounted from the host. The agent writes its notes to the corpus folder. The rules folder holds its tools and instructions and is mounted read-only by adding :ro to the mount entry, as the Hermes configuration guide shows. That means the agent cannot change the script that does the work.

The profile’s terminal settings look like this:

terminal:
  backend: docker                # run commands in a container, not on the Mac
  docker_forward_env: []         # pass no host secrets into the container
  docker_volumes:
    - "/Users/you/research/corpus:/workspace/corpus"    # read-write: the agent's notes
    - "/Users/you/research/rules:/workspace/rules:ro"   # read-only: tools and instructions

To test this, we asked the agent to run touch /workspace/rules/probe. It failed with Read-only file system, while the same command in the corpus folder succeeded.

Keep the agent’s own state under approval and secrets out of the sandbox

The container protects commands, not the profile. Memory, skills and scheduled jobs live on the host, where Hermes’s own tools can change them. We turned on approval for skill and memory writes (skills.write_approval and memory.write_approval), and [a test skill write / a test memory write / test writes to both] stayed pending. [Scheduled jobs are not under approval in this setup; we list that as a gap at the end. — or describe the control you used.]

Secrets need their own check, because skills can ask Hermes to pass environment variables into the container. We asked the agent to load all 57 skills available to the profile, then ran env inside the sandbox. It showed no API keys or tokens.

Test what the tool does, not what the page says

Hermes’s work-machine guide says dangerous-command checks are skipped on container backends. On ours they were not: we created a test folder, and deleting it with rm -r asked for approval. This is one of the two unexpected behaviours we flagged in our introduction; the other, the sandbox refusing to start while the proxy is off, is covered below.

Our never-run rule, *curl*|*sh*, blocked the harmless echo "curl|sh" and let echo "flush" run. Because it matches the text of a command rather than what the command does, we treat it as a backstop behind the container, not a control in its own right.

Unattended jobs use approvals.cron_mode: deny. Our first test, a one-off job that tried to delete a test folder, was refused for the wrong reason. A stray full stop in the job’s instructions made the agent also try rm -r ., and a different built-in block refused that. We noticed only because the refusal message pointed to the wrong rule. When we re-ran the job with the command on a line of its own, the cron_mode setting refused it as intended. Check why a test passed, not just that it did.

Limit the network, then close the tools that bypass it

Hermes’s egress proxy for the Docker backend gives the sandbox stand-in tokens for the providers it covers and answers 403 to any destination not on its allowlist. Because our model calls come from Hermes on the host, not from the sandbox, the stand-in tokens don’t apply to us; for this demo, the proxy’s job is the allowlist. Setting it up taught us two things. First, as the documentation states, the sandbox will not start while the proxy is enabled but not running, so after a reboot the agent stays down until the proxy is back. Part 2 covers starting it at login. Second, the setup wizard expects a provider key, because the proxy’s job is to keep real keys on the host. We sign in with a subscription rather than a key, so we ran the wizard once with a throwaway value. That workaround may stop working in later versions.

From inside the sandbox, the NVD API, which we had added to the allowlist, returned 200, and example.com failed with CONNECT tunnel failed, response 403. The same request from your own Terminal succeeds, because your Mac is not behind the proxy, so run these tests from inside the sandbox.

Two further tests showed the allowlist’s limits. api.openai.com, one of Hermes’s built-in AI-provider hosts, got through, because we had added our hosts without removing the defaults. And a command told to ignore the proxy, curl --noproxy '*' https://example.com, returned 200: the container sits on Docker’s default network, so the request went straight out and never reached the allowlist or the proxy log. The proxy is a filter for well-behaved commands, not a network barrier. Until that route is closed, anything the sandbox can read could be sent out by a manipulated agent, so we keep sensitive data out of it.

The proxy covers only commands run in the sandbox, and several Hermes tools reach the internet without going through it. The tool list showed web, browser, computer-use, connections and others enabled, and scheduled jobs start with every tool on. We switched off every tool the job does not need, for both chat and scheduled jobs. Check this in the hardened profile’s own chat, because the desktop app runs a different profile.

Finally, we planted two baited advisories. One told the agent to curl an outside address, and the other told it to save a rule to memory. The agent ignored both and did not mention them. The proxy log showed no attempt, and the memory folder stayed empty[, and no memory write was waiting for approval]. Because the agent never tried, this test shows how the model behaved, not whether the controls work. Its silence is not a control; the container is. The allowlist only partly is: a command that skipped the proxy would not appear in its log.

What we'd add next

  • A spend-limited model key. Our introduction lists a spending limit as a condition, but our subscription login has no key to limit. A spend-limited key is the next control to add. Like everything else secret, it would stay on the host, out of the sandbox, and it would also end the throwaway-key workaround in the proxy wizard.
  • A real network barrier. Remove Hermes’s built-in AI-provider hosts from the allowlist, and cut the container’s direct route so the proxy is the only way out. Docker Desktop’s air-gapped containers or a separate VM with network-level egress rules would do this; neither is built here.
  • Repeated tests and reviews. Re-run every test after each Hermes update. After unattended runs, check the container and the profile’s home folder for changes.

With these controls in place, the agent can run a real job without changing its own tools or holding a secret, and commands that use the proxy can reach only the hosts on its allowlist. Part 2 builds a daily Citrix patch watch inside this container, designed so that the agent never reads the advisories itself.

Share