Independent Security Audit Report
I ran a comprehensive security audit on the hermes-agent codebase (v0.8.0, 812 Python files, ~364K lines) before deciding whether to install it. Sharing the findings here so other users can make informed decisions.
TL;DR: No malware or data exfiltration found. The code is well-intentioned. But the default security posture is ALLOW-ALL, which creates real risks for users who don't know to harden it.
Methodology
Three parallel audits covering:
- Supply chain — dependencies, lockfiles, Dockerfile
- Data exfiltration & secrets — telemetry, external URLs, API key handling
- Shell execution & code execution — sandbox analysis, file access, skills system
Good News First
- No malware, backdoors, or data exfiltration found
- No telemetry or phone-home behavior — all external URLs are user-configured LLM endpoints or documented features
- API keys sent only to intended providers — verified across the entire codebase
- Comprehensive log redaction — 30+ API key patterns masked via RedactingFormatter
- SSRF protection — blocks private IPs, cloud metadata endpoints
- File permissions — config files properly set to 0o600
- Credential stripping — local environment strips ~60+ env vars from subprocess
- uv.lock with SHA-256 hashes — strong supply chain protection when used correctly
- Skills guard — ~80 regex threat patterns (exfiltration, injection, destructive ops, persistence, reverse shells, crypto mining)
Critical Findings (4)
C1: Unrestricted shell command execution (local backend)
- File:
tools/environments/local.py:254-276, tools/terminal_tool.py:1133
- The terminal tool passes arbitrary commands to
bash -c via subprocess.Popen. The LLM can execute ANY shell command on the host when env_type="local" (the default).
- Dangerous command detection is regex-based and bypassable.
C2: Full filesystem read access with no deny list
- File:
tools/file_tools.py:62-91
- The
read_file tool can read any file on the filesystem. No read deny list exists — the agent can read /etc/passwd, SSH keys, .env files, etc. The redact_sensitive_text function is applied to output only, not as access control.
C3: Approval bypass for containerized environments
- File:
tools/approval.py:591-592
- When
env_type is docker/singularity/modal/daytona, ALL approval checks are unconditionally skipped: return {"approved": True, "message": None}.
C4: LLM can create and execute persistent skills without sandbox
- File:
tools/skill_manager_tool.py:1-33
- The agent can create skills (SKILL.md files) to
~/.hermes/skills/ that are loaded in future sessions. These become persistent prompt injection vectors. The skills guard is regex-only and agent-created skills get "ask" verdict instead of "block" for dangerous findings.
High Findings (9)
| # |
Finding |
File |
| H1 |
YOLO mode (HERMES_YOLO_MODE=1) disables ALL security checks |
tools/approval.py:596-597 |
| H2 |
Smart approval: LLM auto-approves commands via auxiliary LLM (prompt-injectable) |
tools/approval.py:756-767 |
| H3 |
File write deny list bypassable via terminal tool (echo "..." > ~/.ssh/authorized_keys) |
tools/file_operations.py:45-79 |
| H4 |
HERMES_WRITE_SAFE_ROOT is opt-in, not default — wide-open filesystem by default |
tools/file_operations.py:82-96 |
| H5 |
Gateway hooks execute arbitrary Python from ~/.hermes/hooks/ |
gateway/hooks.py:107-115 |
| H6 |
Plugin system loads arbitrary code via importlib with no sandboxing |
hermes_cli/plugins.py:448-494 |
| H7 |
execute_code tool runs Python as regular subprocess — not a true sandbox |
tools/code_execution_tool.py:1-70 |
| H8 |
Skills guard is regex-only static analysis — fundamentally bypassable |
tools/skills_guard.py:82-448 |
| H9 |
3 git dependencies without commit pinning ([rl]/[yc-bench] extras) |
pyproject.toml |
Medium Findings (9)
| # |
Finding |
File |
| M1 |
Non-interactive mode auto-approves ALL commands |
tools/approval.py:608-611 |
| M2 |
Multiple shell=True subprocess calls |
cli.py:5134, hermes_cli/memory_setup.py:207 |
| M3 |
Reads ~/.claude/.credentials.json (Claude Code OAuth tokens) |
agent/anthropic_adapter.py:301-342 |
| M4 |
API keys stored in plaintext (no encryption at rest) |
hermes_cli/config.py:156-157 |
| M5 |
requirements.txt has zero version pinning |
requirements.txt |
| M6 |
Dockerfile: npm install --no-audit suppresses vulnerability warnings |
Dockerfile |
| M7 |
Dockerfile: --break-system-packages mixes system and app packages |
Dockerfile |
| M8 |
Tirith security scanner defaults to fail-open |
tools/tirith_security.py:74 |
| M9 |
SSH auto-accepts new host keys (StrictHostKeyChecking=accept-new) |
tools/environments/ssh.py:64 |
Recommendations
For Users
- Never install on a machine with sensitive keys/wallets — use Docker or a dedicated VPS
- Set
HERMES_WRITE_SAFE_ROOT to sandbox file writes
- Use Docker/Modal backend instead of local
- Never enable YOLO mode
- Set
tirith_fail_open: false
- Install via
uv pip install -e "." with uv.lock — never via requirements.txt
- Install only needed extras — not
[all]
For Maintainers
- Consider making
HERMES_WRITE_SAFE_ROOT opt-out instead of opt-in
- Add a read deny list to
file_tools.py matching the existing write deny list
- Consider blocking terminal tool from writing to paths on the write deny list
- Change tirith default to
fail_open: false
- Pin git dependencies in
[rl]/[yc-bench] extras to specific commits
- Add version pins to
requirements.txt or remove it with a note to use uv.lock
- Consider AST-based analysis for skills guard instead of regex-only
Audit performed using parallel static analysis agents across all 812 Python files.
Independent Security Audit Report
I ran a comprehensive security audit on the hermes-agent codebase (v0.8.0, 812 Python files, ~364K lines) before deciding whether to install it. Sharing the findings here so other users can make informed decisions.
TL;DR: No malware or data exfiltration found. The code is well-intentioned. But the default security posture is ALLOW-ALL, which creates real risks for users who don't know to harden it.
Methodology
Three parallel audits covering:
Good News First
Critical Findings (4)
C1: Unrestricted shell command execution (local backend)
tools/environments/local.py:254-276,tools/terminal_tool.py:1133bash -cviasubprocess.Popen. The LLM can execute ANY shell command on the host whenenv_type="local"(the default).C2: Full filesystem read access with no deny list
tools/file_tools.py:62-91read_filetool can read any file on the filesystem. No read deny list exists — the agent can read/etc/passwd, SSH keys,.envfiles, etc. Theredact_sensitive_textfunction is applied to output only, not as access control.C3: Approval bypass for containerized environments
tools/approval.py:591-592env_typeis docker/singularity/modal/daytona, ALL approval checks are unconditionally skipped:return {"approved": True, "message": None}.C4: LLM can create and execute persistent skills without sandbox
tools/skill_manager_tool.py:1-33~/.hermes/skills/that are loaded in future sessions. These become persistent prompt injection vectors. The skills guard is regex-only and agent-created skills get "ask" verdict instead of "block" for dangerous findings.High Findings (9)
HERMES_YOLO_MODE=1) disables ALL security checkstools/approval.py:596-597tools/approval.py:756-767echo "..." > ~/.ssh/authorized_keys)tools/file_operations.py:45-79HERMES_WRITE_SAFE_ROOTis opt-in, not default — wide-open filesystem by defaulttools/file_operations.py:82-96~/.hermes/hooks/gateway/hooks.py:107-115hermes_cli/plugins.py:448-494execute_codetool runs Python as regular subprocess — not a true sandboxtools/code_execution_tool.py:1-70tools/skills_guard.py:82-448[rl]/[yc-bench]extras)pyproject.tomlMedium Findings (9)
tools/approval.py:608-611shell=Truesubprocess callscli.py:5134,hermes_cli/memory_setup.py:207~/.claude/.credentials.json(Claude Code OAuth tokens)agent/anthropic_adapter.py:301-342hermes_cli/config.py:156-157requirements.txthas zero version pinningrequirements.txtnpm install --no-auditsuppresses vulnerability warningsDockerfile--break-system-packagesmixes system and app packagesDockerfiletools/tirith_security.py:74StrictHostKeyChecking=accept-new)tools/environments/ssh.py:64Recommendations
For Users
HERMES_WRITE_SAFE_ROOTto sandbox file writestirith_fail_open: falseuv pip install -e "."withuv.lock— never viarequirements.txt[all]For Maintainers
HERMES_WRITE_SAFE_ROOTopt-out instead of opt-infile_tools.pymatching the existing write deny listfail_open: false[rl]/[yc-bench]extras to specific commitsrequirements.txtor remove it with a note to useuv.lockAudit performed using parallel static analysis agents across all 812 Python files.