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

Tutorial 9: Security Settings

Tutorial 9: Security Settings — easy-to-understand guide based on official docs

Security: give the AI an access badge

Tutorial 9: Security Settings

Welcome back, agent wranglers! In this tutorial, we’re diving into one of the most important—and often most confusing—parts of running an AI agent: security. Hermes Agent is built with a “defense-in-depth” model, which is a fancy way of saying it has multiple layers of protection so that if one fails, others still have your back.

Think of it like a castle: you wouldn’t just rely on a moat. You’d also want walls, guards, and a sturdy front door. Hermes has eight layers like that. Today, we’re focusing on the ones you’ll interact with most: dangerous command approval and YOLO mode.

The Approval System: Your Human-in-the-Loop

The core idea is simple: before Hermes runs a command that looks dangerous (like deleting files or changing system settings), it checks with you first. This is configured in ~/.hermes/config.yaml under the approvals section.

Here’s what the default setup looks like:

approvals:
  mode: smart                     # smart | manual | off
  timeout: 300                    # seconds to wait for user response
  cron_mode: deny                 # deny | approve — for cron jobs
  single_query_mode: deny         # deny | approve — for -q sessions
  unattended_mode: deny           # deny | approve — for webhook/API sessions
  mcp_reload_confirm: true        # confirm before reloading MCP tools
  destructive_slash_confirm: true # confirm before /clear, /new, etc.

The most important key is mode. It has three settings:

  • smart (default): Hermes uses a separate LLM to judge risk. Safe commands (like python -c "print('hello')") are auto-approved. Truly dangerous ones are auto-denied. If it’s uncertain, it asks you.
  • manual: Every dangerous command triggers a prompt. No exceptions.
  • off: All checks are disabled. This is the same as running with --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. Only use this in trusted environments like CI/CD pipelines.

The other keys are about specific situations. For example, cron_mode controls what happens when a scheduled job hits a dangerous command. Since no human is waiting to answer, the default is deny—the agent has to find another way. The single_query_mode does the same for one-shot hermes chat -q sessions, and unattended_mode does it for sessions on unattended programmatic platforms (webhook, msgraph_webhook, api_server). Those surfaces have no human who can answer /approve, so instead of blocking for the full approval timeout, deny blocks the command instantly and approve auto-approves everything in unattended context. One exception: an api_server session whose client can answer the card (/v1/runs and streaming chat completions, via POST /v1/runs/{id}/approval) still gets the approval request.

You’ll also notice destructive_slash_confirm. When set to true (the default), commands like /clear, /new, /reset, and /undo will ask for confirmation before wiping your conversation. On Telegram, Discord, and Slack, this shows up as native yes/no buttons. Nice touch, right?

One more thing worth knowing: if an approval prompt times out, it can’t be reopened. The pending request 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 (for example, “go ahead and run that now”)—the agent issues a fresh tool call, which raises a fresh approval card. A timeout is not counted as a denial, so asking again is never penalized.

Content-Level Command Checks

The dangerous-command detector also flags two content-level shapes that a destructive-verb list misses:

  • 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: With Great Power…

Sometimes you just want the agent to run without pestering you for approval. That’s what YOLO mode is for. It bypasses all dangerous command 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 or 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 can’t forget it. You’ll see a red banner at session start and a ⚠ YOLO indicator in the status bar, updated live as you toggle it.

Important warning: YOLO mode disables all safety checks except the hardline blocklist—commands that are never allowed, no matter what. Use it only when you fully trust the commands being generated, like in a disposable test environment.

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.

Supervised-Gateway Lifecycle Restriction

One guard you can’t switch off: the terminal tool refuses to stop or restart the gateway from inside its own supervised process, since 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 same guard also refuses 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 literally 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 unit, launchd plist, s6 run script, and the Windows Scheduled Task.

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.

Editing Control Files Safely

One habit worth building early: don’t ask the agent to patch control files like ~/.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 applies to other Hermes control files when write safety blocks direct edits.

TLS Certificate Trust

Hermes 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. Hermes’ provider resolver no longer selects trust through HERMES_CA_BUNDLE or the old CA-environment-variable ladder. Sandboxed subprocesses can have their own separate CA configuration. The former startup certificate guard is gone with it: Hermes no longer validates HERMES_CA_BUNDLE / SSL_CERT_FILE / REQUESTS_CA_BUNDLE at startup.

What About File Writes and Containers?

While we’re focusing on approvals today, remember that Hermes also protects you in other ways. It has a denylist for write_file and patch operations, and it can sandbox file writes entirely. Container isolation (Docker, Modal, and the Singularity plugin) is another layer, and MCP subprocesses get their own environment variable filtering to keep credentials separate.

If you’re using the sandboxed desktop image, the default is:

docker_image: "nousresearch/hermes-sandbox:desktop"

One more rule to remember: Hermes infrastructure secrets (provider API keys, gateway tokens) should never be added to env_passthrough—they have dedicated mechanisms. Such a name is refused when declared, and a declared name that a platform adapter claims later (a plugin adapter registering after the skill loaded) stops being forwarded from then on.

There’s even prompt injection detection that scans your project files for malicious instructions, and cross-session isolation so one session can’t peek into another’s data.

Wrapping Up

Security doesn’t have to be scary. With Hermes, you get a smart default (smart mode) that balances safety and speed. Start there. As you get more comfortable, you can tweak the settings to match your workflow—just be careful with that YOLO switch.

Next time, we’ll look at cron jobs and how they interact with these security layers. Until then, keep your approvals on and your commands safe!

📖 Official Docs

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