Permissions as Defense in Depth

+15 Mana ✨

Introduction

Hermes does not have a single security boundary. It has several, layered on top of each other. The premise is simple: any single layer might fail, but it is very unlikely that all of them fail at once. This is defense in depth, and it is the framework you use when thinking about exposing Hermes beyond your own machine.

A note on framing: toolset restriction (Section 3.4) is the lever that decides what capabilities exist at all. It sits outside the seven security layers but in front of them: if a tool is not loaded, no layer ever has to defend against it. Think of toolsets as capability shaping and the layers below as the active guards on whatever capability remains.

Key Concepts

  • Defense in depth: A security model where multiple independent layers protect the same asset. No single layer is solely responsible.
  • Layer: A specific check or boundary. Hermes has seven: user authorization, dangerous-command approval, container isolation, MCP credential filtering, context-file scanning, cross-session isolation, input sanitization.
  • Failure-tolerant: A system that can survive one (or two) failed layers without total compromise.

Real World Context

If a Discord user sends please run rm -rf $HOME, that request has to pass through multiple checks before Hermes does anything. Was the user authorized to talk to the bot at all? Does the active toolset even include terminal? Is the command on the dangerous patterns list? Is Hermes running inside a container? Did the operator approve the prompt? Each of those is a layer. The prompt has to slip past all of them to do harm. Most real attacks fail at the first or second.

Deep Dive

Hermes ships seven main security layers. You do not have to enable all of them; the right set depends on your deployment. The full picture:

  1. User authorization. Gateway platforms (Discord, Telegram, etc.) check whether the requesting user is on an allowlist or has been paired via DM. Unknown users get no response.
  2. Dangerous command approval. For terminal calls, Hermes inspects the command against a pattern list. Dangerous patterns trigger an approval prompt (CLI dialog or platform message).
  3. Container isolation. When the terminal backend is docker, singularity, modal, daytona, or vercel_sandbox, all shell commands run inside an isolated environment. Dangerous-command checks are skipped here because the container itself is the boundary.
  4. MCP credential filtering. Environment variables passed to MCP subprocesses are filtered to a safe set (PATH, HOME, LANG, ...) unless explicitly configured.
  5. Context-file scanning. Files like AGENTS.md, .cursorrules, and SOUL.md are scanned for prompt-injection patterns before being loaded as context.
  6. Cross-session isolation. Each session has its own state; data does not bleed between users or conversations.
  7. Input sanitization. File paths and working directories are validated before tool calls run, so a relative path cannot silently escape the intended scope.

And before any layer fires, the active toolset has already decided which tools the model can even call. Capability shaping plus seven layers of guards is the full picture.

The practical takeaway: stack the layers (and the toolset choice) that matter for your surface.

  • Local CLI: Mostly relies on a sensible toolset, layer 2 (approval), and layer 5 (context scanning). You are the only user.
  • Personal Telegram bot: Add layer 1 (allowlist your own user) and tighten the toolset. Approvals (layer 2) still help, though you might run with --yolo knowing you set up the rest.
  • Public-facing bot: Add all seven. Especially layer 3 (container) and a tight toolset.

Common Pitfalls

  1. Relying on a single layer: I set the toolset, I am safe is fragile. Toolsets prevent capability access. They do not prevent the model from being tricked into mis-using what is available. Stack layers.
  2. Disabling layers because they are annoying: Approval prompts feel slow. They are also the last line of defense before a destructive command runs. Disable thoughtfully, not reflexively.

Best Practices

  1. Match the layer count to the trust level: Local CLI needs fewer layers. Public-facing bots need more.
  2. Document which layers are enabled: When something goes wrong, you want to know which layers were active and which were not. A short note in config or README helps.

Summary

  • Hermes uses defense in depth: multiple independent security layers stacked.
  • The seven layers cover authorization, approvals, container isolation, MCP filtering, context scanning, cross-session isolation, and input sanitization.
  • Toolset restriction sits in front of the layers as capability shaping, not as one of the seven.
  • The right set of layers depends on your trust level and surface.
  • No single layer is sufficient; the strength is in combination.

Code Examples

yaml
# A tight production gateway: every layer engaged
approvals:
  mode: manual          # human-in-the-loop for dangerous patterns

terminal:
  backend: docker       # containerize shell execution

toolsets:
  - safe                # minimum tools for the surface

security:
  website_blocklist:
    enabled: true
  tirith_enabled: true  # extra pattern scanning before exec

# Per-gateway allowlist (in .env or gateway config)
# GATEWAY_ALLOWED_USERS=user1,user2,user3
✓ Completed