Security Audit
Internal
Keypad or type your PIN
Security Audit
Internal Security · Aug 2026

Security Audit #3

Hermes agent configuration review — 06 Aug 2026. Two critical exposures, three unresolved high-risk gaps carried from Audit #2, and a clean bill for a hardened core.

FACTS verified against live config and telemetry. FIXES are one-line commands. EFFORT estimates in minutes.
Zeus · 06 Aug 2026 · Internal
Section 01 · Overview

Scope & Severity Summary

Audit #3 of the Hermes agent configuration. Prior audit ran 28 Jun 2026 (Audit #2: 2 Critical / 3 High / 4 Medium). This pass reviews v0.18.2 (2026.7.7.2), upstream commit eb527605, local 7b5ba205.

Critical
2
act now
High
2
this week
Medium
4
short-term
Low
4
housekeeping
Passing
7
hardened
SeverityCountStatus
Critical21 live credential exposure + 1 silent no-op guardrail
High2both carried over from Audit #2, still unresolved
Medium4hardening opportunities, ~1-5 min each
Low4file-permission cleanup + note
Passing7secrets, firewall, SSRF, MCP, cron — all clean
Section 02 · Critical

Critical Findings

Two genuine exposures — one is the only real credential leak found in this audit.

🔴 1. Live API key in config.yaml (not .env): custom_providers[Qwen3.6].api_key holds a real 67-char key. Secrets belong in .env (mode 600), not plaintext config. This is the one genuine credential exposure found.
Fix: move to .env as QWEN36_API_KEY=..., reference in config as api_key: ${QWEN36_API_KEY} or provider env var. ~2 min.
🔴 2. Tirith guardrail enabled but NOT installed — silent no-op: config.yaml has tirith_enabled: true, tirith_path: tirith, tirith_fail_open: false — but which tirith and pip show tirith both fail. The config promises prompt-injection/tenant guardrails that aren't actually running. Fail-open is false, so either it's silently no-oping or blocking tool calls unpredictably. False sense of security.
Fix: install tirith, or set tirith_enabled: false to stop advertising protection that isn't there. ~5-10 min.
Section 03 · High

High Findings

Both carried over from Audit #2 and still not addressed — they are the largest residual risk surface.

🟠 3. Massive version gap: Current v0.18.2 → latest v2026.8.3. 19,616 commits behind, ~3,109 security-relevant commits (inject/tirith/guard/sanitize/redact/leak/vuln/auth/approval/harden). This is the single biggest risk surface — fixes, injection hardening, and guardrail improvements all unapplied. Carried over from Audit #2.
Fix: staged upgrade with backup + rollback plan. Effort high, biggest payoff. ~30+ min.
🟠 4. Telegram allowed_chats is empty: Only TELEGRAM_ALLOWED_USERS (user-ID) restricts. If the bot gets added to an untrusted group, any chat can trigger it. Carried from Audit #2.
Fix: set allowed_chats to your chat ID (944548986). ~1 min.
Section 04 · Medium

Medium Findings

Hardening opportunities — none are active breaches, but each closes a supply-chain or data-handling gap.

🟡 5. allow_lazy_installs: true: Agent may auto-install packages mid-task without asking (supply-chain risk). INSTALL GATE memory already covers this — make it enforced in config. ~1 min.
🟡 6. redact_pii: false: Sensitive personal data not auto-redacted in output. ~1 min to enable.
🟡 7. website_blocklist enabled but domains: []: Framework armed, zero domains listed. Not a risk by itself, but no active protection. ~1 min to populate.
🟡 8. approvals.mode: manual + auxiliary.approval unset: Manual mode only gates destructive patterns (rm, git reset, dd); most commands auto-approve silently. With cron_mode: deny, cron is safe, but interactive sessions don't ask before non-destructive ops. Consider smart mode with an approval model. ~5 min.
Section 05 · Low

Low Findings

Housekeeping — mostly file-permission hardening; quick wins that reduce local attack surface.

🟣 9. Google OAuth token files world-readable (644): google_token.json (live access + refresh tokens) and google_client_secret.json both 644. Should be 600. ~1 min: chmod 600.
🟣 10. hermes_state.db + state.db (1.1GB) + kanban.db 644: Full conversation history / state DBs world-readable. Fix early. ~1 min.
🟣 11. One stale config backup 644: config.yaml.bak.20260629_223258 is 644 (contains config, possibly creds). Newer backups are 600. ~1 min.
🟣 12. No guest profile: Not a finding against posture — just noting guest sandbox isn't in use. No action needed unless you plan untrusted access.
Section 06 · Passing

Passing Checks

Seven controls verified clean — the hardened core. Notable improvement: SSRF defense is now explicit in both contexts.

✅ 1. .env mode 600 (owner-only), all keys redacted in audit.
✅ 2. .env in .gitignore; never committed to git history.
✅ 3. macOS firewall enabled (State=1).
✅ 4. Ollama binds 127.0.0.1:11434 only (not 0.0.0.0).
✅ 5. allow_private_urls: false in BOTH browser and security contexts (SSRF defended — IMPROVED since Audit #2, which flagged it true).
✅ 6. MCP servers are local binaries only; no remote endpoints; no API keys in MCP env vars.
✅ 7. Cron jobs: 15/17 disabled; only Macro Watch + one-shot gbrain reminder active. Delivery targets all trusted (telegram/origin/local).
✅ 8. No secrets in MCP server env.
Section 07 · Summary

Prioritized Fix Table

All 12 findings ranked by severity × effort — highest risk for least effort first. The two Critical fixes total ~12 minutes.

#FindingSevEffortFix
1Qwen key in config.yamlCritical2 minmove to .env
2Tirith enabled, not installedCritical5-10 mininstall or disable
4allowed_chats emptyHigh1 minset chat ID
319,616 commits / 3,109 sec behindHigh30+ minstaged upgrade
5lazy installs onMedium1 minallow_lazy_installs: false
6redact_pii offMedium1 minenable
7blocklist emptyMedium1 minpopulate domains
8manual approvalsMedium5 minconsider smart mode
9google tokens 644Low1 minchmod 600
10state/kanban db 644Low1 minchmod 600
11stale config backup 644Low1 minchmod 600
12no guest profileLown/a
Methodology: Static review of ~/.hermes/config.yaml, .env, MCP server config, cron jobs, and file permissions on 06 Aug 2026. Findings verified live where applicable (e.g. which tirith, pip show tirith, chmod state, git history). Effort estimates are single-operator minutes. No external systems consulted — this is an internal configuration audit.