🤖HermesBlog
Hermes Official Tutorials · Part 298/9/2026

Tutorial 29: Security Deep Dive

Tutorial 29: Security Deep Dive — easy-to-understand guide based on official docs

Security deep dive: eight-layer onion

Tutorial 29: Security Deep Dive

Welcome back! Today we’re going to talk about something super important: security. If you’ve been following along with our tutorials, you know that Hermes Agent is powerful. And with great power comes great responsibility—which is why Hermes has a seriously robust security model built right in.

Think of it like a castle with eight layers of defense. Each layer protects you from a different kind of threat, from sneaky prompt injections to accidental destructive commands. Let’s walk through the highlights.

The Eight Layers of Defense

Hermes doesn’t rely on just one security measure. Instead, it layers them up:

  1. User authorization — Only approved people can talk to the agent.
  2. Dangerous command approval — A human must approve risky operations.
  3. File write safety — Protects against unwanted file modifications.
  4. Container isolation — Runs tasks in sandboxes like Docker, Modal, and the Singularity plugin.
  5. MCP credential filtering — Keeps secrets away from subprocesses.
  6. Context file scanning — Detects prompt injection in project files.
  7. Cross-session isolation — Sessions can’t peek into each other’s data.
  8. Input sanitization — Prevents shell injection via working directory parameters.

That’s a lot of protection! But the star of the show is the Dangerous Command Approval system. Let’s dig into that.

How Command Approval Works

Before Hermes runs any command, it checks it against a list of dangerous patterns. If there’s a match, you get to approve or deny it. But here’s the cool part: you can configure how that approval works.

In your ~/.hermes/config.yaml, you’ll find the approvals section:

approvals:
  mode: smart                     # smart | manual | off
  timeout: 300                    # seconds to wait for user response
  cron_mode: deny                 # deny | approve
  single_query_mode: deny         # deny | approve
  unattended_mode: deny           # deny | approve
  mcp_reload_confirm: true        # /reload-mcp asks before invalidating cache
  destructive_slash_confirm: true # /clear, /new, /reset prompt first

There are three modes:

Mode What it does
smart (default) Uses an auxiliary LLM to judge risk. Safe commands auto-approve, dangerous ones auto-deny, and uncertain ones ask you.
manual Always asks you for approval on dangerous commands.
off Disables all checks. Equivalent to --yolo. All commands execute without prompts, and write calls on trust: untrusted MCP servers run without the trust prompt. An MCP server’s declared spend limit still asks. Use with extreme caution!

The cron_mode, single_query_mode, and unattended_mode settings are especially handy. They control what happens when a cron job, a one-shot -q session, or an unattended webhook/API session hits a dangerous command. Since there’s no human waiting to answer, the default is deny—the command gets blocked instantly, and the agent has to find another way. There’s one exception: an api_server session whose client can answer the approval card (/v1/runs and streaming chat completions, via POST /v1/runs/{id}/approval) still gets the approval request.

One more thing about prompts: if an approval prompt times out, it can’t be reopened. The pending entry is discarded and the agent is told not to retry on its own within that turn. To run the operation after all, just send a new message asking for it—the agent issues a fresh tool call and you get a fresh approval card. A timeout isn’t counted as a denial, so asking again is never penalized.

Content-Level Command Checks

Pattern matching alone misses a couple of sneaky shapes, so the detector also flags two content-level cases:

  • A curl/wget request body that carries a secret: a secret-named variable (-d "k=$OPENAI_API_KEY") or a credential file (-F file=@.env, -T ~/.ssh/id_rsa, --post-file=/etc/passwd), or a credential file piped into an uploading curl/wget. An Authorization header is ordinary API use and is not flagged.
  • Invisible or bidirectional Unicode control characters (zero-width spaces, right-to-left overrides, isolates), which make the command you approve differ from the one that runs. Emoji joiner sequences are not flagged.

Both go through the normal approval flow like every other dangerous pattern. Earlier releases bundled the external tirith scanner here. It was removed; upgrading drops the security.tirith_* settings and enables nothing in its place.

execute_code Scripts

A script run by execute_code can call subprocess, os or shutil directly, and those calls never pass through the patterns above. Messaging, API and other gateway sessions therefore ask once for the whole script before it starts; a denied or unanswered script never runs. cron_mode, single_query_mode and unattended_mode decide the script at once, as they decide a command; YOLO and mode: off skip the prompt.

Local sessions (classic CLI, TUI, Desktop, ACP editor) follow the interactive CLI’s rule instead. Under manual (and smart), a turn the session’s viewer typed gets no whole-script approval: Python-level file and process operations inside the script run without a prompt, and only the script’s terminal() calls ask, each on the session’s own prompt. The exemption needs both a turn the viewer submitted and a viewer attached who can answer approvals when the script starts. These keep the whole-script approval in a local session:

  • turns nobody typed in it: plugin inject_message turns, delegation completions, background-process and watch-pattern notices, /loop ticks, heartbeats, hosted-room tasks
  • delegate_task children and /bg / /btw side tasks
  • a viewer’s turn that runs while no viewer who can approve is attached

YOLO Mode: The Big Red Button

Sometimes you just want to let the agent run wild. That’s what YOLO mode is for. It bypasses all approval prompts for the current session.

You can activate it three ways:

  1. CLI flag: hermes --yolo or hermes chat --yolo
  2. Slash command: Type /yolo during a session to toggle it on/off
  3. Environment variable: Set HERMES_YOLO_MODE=1

The /yolo command is a toggle, so typing it again turns it off:

> /yolo
  ⚡ YOLO mode ON — all commands auto-approved. Use with caution.

> /yolo
  ⚠ YOLO mode OFF — dangerous commands will require approval.

YOLO mode is available in both CLI and gateway sessions. /yolo applies only to the session it was typed in. It is saved with the session, so it survives a restart: a messaging chat keeps it across gateway restarts, and the TUI and the Desktop app keep it when you reopen or resume the session. On a messaging platform, /new and /resume start the next conversation with approvals back on.

When YOLO is active, Hermes makes sure you don’t forget. You’ll see a red banner at session start and a ⚠ YOLO indicator in the status bar, updated live as you toggle.

⚠️ Warning: YOLO mode disables all dangerous command checks—except the hardline blocklist. Only use it when you fully trust what’s being generated, like well-tested scripts in disposable environments.

YOLO and approvals.mode: off also skip the trust: untrusted MCP prompt: a write-capable tool on an untrusted server runs without asking, with each source’s usual reach. /yolo covers only the chat it was typed in (another chat is still asked); --yolo and HERMES_YOLO_MODE cover the sessions of the Hermes process they were given to; approvals.mode: off covers every chat and cron job of that profile. They do not skip:

  • An MCP server’s spend limit. A call that would take the chat past a declared spend_limit still asks, and where nobody can answer (a cron job, hermes chat -q) it is refused. The limit is a budget you set, not a safety prompt.
  • Questions from an MCP server (elicitation), which ask you for input rather than for permission.
  • Payment-card fill prompts from the credential vault, which hand a card to a page, and MCP event subscriptions, which install a standing trigger.

New in This Update

Two new settings deserve a special shout-out:

  • mcp_reload_confirm — When true, /reload-mcp asks before rebuilding the MCP tool set. This matters because rebuilding invalidates the provider prompt cache, meaning the next message re-sends full input tokens (costs more!). Users who click “Always Approve” flip this to false.

  • destructive_slash_confirm — When true, destructive commands like /clear, /new, /reset, and /undo prompt before discarding your conversation. On Telegram, Discord, and Slack, you get native yes/no buttons. In the TUI, you can force-skip the modal with HERMES_TUI_NO_CONFIRM=1.

Editing Control Files Safely

One quick habit worth building: don’t ask the agent to patch ~/.hermes/cron/jobs.json directly. Use the cronjob_manage tool, hermes cron, or /cron instead—they update the job store through the supported API. The same rule applies to other Hermes control files whenever write safety blocks direct edits.

Supervised-Gateway Lifecycle Restriction

There’s one more guard worth knowing about, and it can’t be turned off. The terminal tool refuses to stop or restart the gateway from inside its own supervised process, because a self-restart can kill the tool mid-run and trigger a supervisor/auto-resume loop. User approval, YOLO mode, and force=True do not bypass this guard.

The guard also blocks process killers aimed at the interpreter image the gateway runs as—things like taskkill /F /IM python.exe, Stop-Process -Name python, pkill -9 python3, killall python, or pgrep python | xargs kill. A supervised gateway is a python process, so those commands would take it (and the agent’s own turn) down. Kills scoped to a process the agent owns still pass: the proc_* id of a background job, or an explicit PID like kill <pid>. Other image names (say, notepad.exe) are unaffected. This guard is active under every generated launcher—systemd, launchd, s6, and the Windows Scheduled Task—via the HERMES_SUPERVISED_CHILD marker they export.

On macOS, executed launchctl submit and launchctl bootstrap commands are restricted regardless of the job label, so for authorized LaunchAgent maintenance, use a separate shell outside the running gateway.

TLS Certificate Trust

Hermes now initializes the platform verifier through truststore: Windows uses its certificate store, macOS uses its system trust services, and Linux uses the OpenSSL system trust paths. If initialization fails, Hermes logs the failure and falls back to OpenSSL defaults.

For a corporate TLS proxy, install its root through your organization’s operating-system trust procedure. The provider resolver no longer selects trust through HERMES_CA_BUNDLE or the old CA-environment-variable ladder, and the former startup certificate guard is gone too—Hermes no longer validates HERMES_CA_BUNDLE, SSL_CERT_FILE, or REQUESTS_CA_BUNDLE. Sandboxed subprocesses can still have their own separate CA configuration.

Keeping Secrets Out of the Sandbox

One more rule to remember: Hermes infrastructure secrets—provider API keys and gateway tokens—should never be added to env_passthrough. They already have dedicated mechanisms, so a name like that is refused the moment you declare it. And if a platform adapter claims a name after the skill has loaded (say, a plugin adapter registers later), that name simply stops being forwarded from then on.

Wrapping Up

Hermes Agent takes security seriously, and now you know exactly how it protects you. The key takeaway? Start with smart mode, keep the defaults, and only switch to YOLO when you’re absolutely sure.

Next time, we’ll explore more advanced features. Until then, stay safe out there!

📖 Official Docs

This article is based on the official Hermes Agent documentation:Official docs › user-guide/security