Hermes LoopHermes Loop
Hermes runtime · policy

Runtime safety policyThe exact rules terminal_exec and python_rpc enforce.

← Runtime

1. Workspace restriction

Terminal commands run with their working directory locked to the project root (process.cwd()). If a caller passes workingDirectory, it is resolved against the project root and rejected if the resolved path escapes via .. traversal. Python RPC scripts run in a fresh os.tmpdir() directory that is removed after the run.

2. Timeout

Default timeout is 30 seconds; the per-call cap is 120 seconds. On timeout the child is sent SIGTERM; if it doesn't exit within 1s it is sent SIGKILL. The recorded exit code on timeout is 124 with a stderr note.

3. Output truncation

Stdout and stderr are each capped at 8 KB. When the cap is hit the tool sets truncated: true in its output. Raw streams are not persisted — only the truncated capture lives in the ToolCall row.

4. Hard-blocked patterns (case-insensitive)

The following intents always fail before execution:

  • Destructive filesystem (rm -rf, rmdir /s, del /s, format, mkfs, dd if=)
  • Power / system (shutdown, reboot, halt, poweroff)
  • Encoded shell (powershell -EncodedCommand, base64 | sh)
  • Pipe-to-shell (curl | sh, wget | sh, IEX downloads)
  • Deployment / publish (git push, vercel deploy, railway up, npm publish)
  • Auth mutation (npm login, npm token)
  • Secret dumping (printenv, env dump, cat .env*, echo $HERMES_API_KEY)
  • Privilege escalation (sudo, su)
  • Force kill (kill -9, Stop-Process -Force)
  • Bare eval / exec

5. Approval-gated risky commands

Anything not on the allow-list (and not blocked) requires an approval before it runs. The executor creates a TOOL_CALL ApprovalItem and parks the call as BLOCKED until you decide. Multi-statement commands (&&, ||, ;, |, redirects) are forced through this path.

6. Safe allow-list (runs without approval)

The allow-list is intentionally narrow:

  • git status / diff / log / branch / show
  • rg (ripgrep) for codebase search
  • npm run lint / typecheck / build / test / ls
  • npx tsc --noEmit
  • npx next lint
  • npx prisma validate / generate
  • ls / dir / pwd
  • cat / type / Get-Content (single-file reads)
  • --version probes for node, npm, npx, python, tsc, next, prisma

7. Secrets are never handed to the child

Before spawn, the runtime strips HERMES_API_KEY, every OPENROUTER_* variable, and every DATABASE_URL* from the child process's environment. So even an allow-listed command like printenv (if it were on the allow-list — it is not) could not exfiltrate them. The child also gets AGENT_FOUNDRY_RUNTIME=1 for self-identification.

8. Python-specific policy

Every python_rpc script needs an approval. The static block-list also catches:

  • os.environ dump (incl HERMES_API_KEY / OPENROUTER_*)
  • subprocess(..., shell=True)
  • os.system
  • os.remove / shutil.rmtree
  • open(...env)
  • exec / eval
  • sockets after import
  • urllib / requests / httpx / aiohttp

9. What this is NOT

This is defense in depth, not a sandbox. The runtime runs inside the host Node process — there is no per-call container, no namespace isolation, no kernel-level filtering. A determined adversary who controls a mission objective could craft inputs that slip past the policy. The proper fix is per-mission Docker / Modal / Singularity backends — see the parity board for status. Until that lands, treat the policy as a guardrail against accidents and obvious abuse, not as an adversarial boundary.