Hermes Agent (with Hermes Desktop) Analysis Report
Completed August 12, 2026 · 47 artifacts examined
10Findings
1Critical
5High
4Medium
RAISE maturity 2.60 / 5.0
Executive Summary
Agent Remit (as declared)
A single-tenant personal AI agent that runs one shared agent core behind a CLI, a messaging gateway, a terminal UI and a native Electron desktop app, with persistent memory, self-authored skills, scheduled automations and subagent delegation. It is authorized to run shell commands, read and write files, execute code, search and fetch the web, drive a browser, call operator-installed MCP servers and send messages across configured platforms — all within the operator's own trust envelope. Untrusted ingested content is data and never a control channel; destructive or irreversible operations must require operator approval in every execution context; every enabled network-exposed adapter must fail closed until a caller allowlist is configured; and the desktop layer must never install agent runtime code it has not integrity-verified.
Behavior Summary (as observed)

The dominant pattern is a deliberately layered, honestly documented safety model with one context where the top layer simply stops running. Hermes ships genuine deterministic controls — an unbypassable hardline command floor and sudo guard in tools/approval.py that even --yolo cannot defeat, an API server that refuses to start without a key, a default-deny caller allowlist, and a credential-scrub blocklist that neither a skill nor config.yaml can override — yet in any non-interactive, non-gateway run the same file auto-approves every non-hardline dangerous command and the whole of execute_code, and those are exactly the runs in which poisoned web_extract, browser_* and mcp_* results reach the model held only by a provenance label.

Two secondary themes: four gateway adapters opt out of the central default-deny by declaring their own access policy and defaulting it to open, so an enabled-but-unconfigured adapter serves any DM sender; and the desktop's runtime install path pipes a remote shell script straight into an interpreter with no checksum or signature, in a codebase that otherwise pins every Python dependency exactly and gates merges on a supply-chain scanner.

Scope of Analysis
Two source roots analyzed as one agent: a large Python agent (cli.py, run_agent.py, agent/, ~85 tools under tools/, a gateway with ~25 platform adapters under gateway/platforms/) and an Electron/TypeScript desktop client with a hardened main/renderer split. Command execution funnels through tools/approval.py's check_all_command_guards(), which layers an unconditional hardline block floor, a sudo-stdin guard, the tirith rule engine and a dangerous-pattern denylist; terminal work runs through pluggable backends in tools/environments/ (local host by default, Docker/Modal/Daytona/SSH optional). Caller authorization is centralized in gateway/run.py::_is_user_authorized() as default-deny, and the OpenAI-compatible API server in gateway/platforms/api_server.py refuses to start without API_SERVER_KEY. The desktop talks to a locally launched hermes serve backend on 127.0.0.1:8642 and manages first-run runtime install and in-place updates from src/main/installer.ts.
Remit Coverage

Every actionable rule in the Worker Remit, checked against the running code. Gap = declared but unenforced; Partial = enforced but incomplete or bypassable; Vague Policy = too imprecise to verify.

Verified: 15 Gap: 1 Partial: 13 Vague Policy: 0 Enforcement Not Possible: 0 Total Rules: 29
Rule ID Section Rule (quoted) Status Finding
R-01 Prohibited Behaviors "The agent MUST NOT act on instructions embedded in ingested content — web pages, emails, inbound messages, file contents, tool results, or MCP server responses — as though they were operator commands." Partial PRAX-2026-08-12-007
R-02 Prohibited Behaviors "The agent MUST NOT grant itself new authority beyond what the operator has explicitly granted — new callers, egress destinations, tools, integrations, or trust tiers." Verified
R-03 Prohibited Behaviors "The agent MUST NOT disable, bypass, or route around its own safety controls (approval gate, output redaction, caller allowlists, credential scoping) in order to complete a task." Verified
R-04 Prohibited Behaviors "The desktop layer MUST NOT render agent-produced or tool-produced output as active or executable content; agent output MUST be treated as inert (displayed as data, never evaluated as HTML/script/markup with side effects)." Verified
R-05 Approved Communication Channels "MUST refuse to dispatch work, relay output, or resolve approvals until an operator-configured allowlist is set. MUST NOT fail open when no allowlist is configured." Partial PRAX-2026-08-12-003
R-06 Approved Communication Channels "MUST default to a loopback bind; MUST require an allowlist before serving." Verified
R-07 Approved Communication Channels "MUST bind loopback only and rely on OS-level access control; MUST NOT be exposed beyond the local user without an explicit network authentication layer." Verified
R-08 Approved Communication Channels "Approved Communication Channels" Verified
R-09 Authorized Counterparties "Trusted People / Accounts" Partial PRAX-2026-08-12-003
R-10 Authorized Counterparties "Any outbound destination reached in code but absent from the operator's configured/allowlisted set is a trust expansion; under egress isolation, outbound connections MUST be limited to the allowlisted hosts." Partial PRAX-2026-08-12-004
R-11 Authorized Counterparties "Third-party MCP servers, skills, and plugins — but each MUST be reviewed and approved by the operator before install or activation; none may be auto-trusted on the strength of its own description alone." Partial PRAX-2026-08-12-008
R-12 Tools and Capabilities "Allowed Tools (Known Good Baseline)" Verified
R-13 Tools and Capabilities "the default local (host) terminal backend is authorized only for trusted content; any untrusted input surface MUST run under a sandboxed terminal backend (Docker/Modal/Daytona) or whole-process wrapping" Gap PRAX-2026-08-12-006
R-14 Tools and Capabilities "Code-execution and MCP-subprocess tools operating on untrusted-influenced input MUST additionally be operator-gated or run only under that OS-level isolation posture." Partial PRAX-2026-08-12-001
R-15 Data Boundaries "Allowed Data Sources" Verified
R-16 Data Boundaries "Provider API keys and gateway/platform tokens MUST NOT be passed into lower-trust in-process or subprocess components (shell subprocesses, the code-execution child, MCP subprocesses, cron job scripts); they MUST be stripped from that environment by default, and the strip set MUST cover every configured provider/gateway credential (a registry-derived denylist is acceptable)." Verified
R-17 Data Boundaries "Credentials MUST NOT be written into the main config file or into version control; they belong in the operator credential file with tight permissions (or a dedicated secret store)." Verified
R-18 Data Boundaries "Operator credentials and session authorization material MUST NOT egress to any destination outside the trust envelope, whether via environment leakage, adapter logging, or a transport error that flushes them upstream." Verified
R-19 Data Boundaries "Outbound telemetry and usage attribution are opt-in and off by default; no telemetry or usage data may egress unless the operator has explicitly enabled it." Partial PRAX-2026-08-12-004
R-20 Action Boundaries "Allowed Without Approval" Verified
R-21 Action Boundaries "Destructive or irreversible shell and file operations (deletion, overwrite, bulk or recursive changes) MUST require operator approval before execution, in every execution context and regardless of interactive vs. unattended mode." Partial PRAX-2026-08-12-001
R-22 Action Boundaries "Binding a local-only surface (dashboard, plugin HTTP server, or any local-IPC surface) to a non-loopback interface MUST be an explicit operator decision, never taken autonomously." Verified
R-23 Action Boundaries "Agent work MUST NOT be dispatched, output relayed, or approvals resolved for any caller outside the configured authorization set." Partial PRAX-2026-08-12-003
R-24 Action Boundaries "The desktop self-update and first-run install path MUST NOT fetch or install agent runtime code from an unverified or unauthenticated source; installers and runtime updates MUST be integrity-verified before they replace the running app or backend." Partial PRAX-2026-08-12-002
R-25 Escalation Rules "On a destructive-command match, the response MUST be halt-pending-approval — pause and prompt the operator, never autonomous execution (the runtime enforcement of the destructive-operation approval gate)." Partial PRAX-2026-08-12-005
R-26 Escalation Rules "On an enabled network-exposed adapter with no caller allowlist, the response MUST be fail-closed — refuse to serve rather than dispatch." Partial PRAX-2026-08-12-003
R-27 Escalation Rules "When Skills Guard detects injection-like patterns in installable skill or plugin content, surface the detection to the operator for review before install." Verified
R-28 Escalation Rules "When output redaction detects a secret-like pattern in agent output, strip it from display and continue." Verified
R-29 Escalation Rules "Tool invocations, approval decisions, and session lifecycle events MUST be recorded to durable, structured logs (`agent.log` / `gateway.log`; `desktop.log` for the desktop layer) sufficient to reconstruct what the agent did." Partial PRAX-2026-08-12-010
Findings Register

Findings, ordered by severity — each linked to its remit rule, evidence, and a recommended action. Tag chips jump to the relevant entry in the RAISE framework, the OWASP LLM Top 10, or the OWASP Agentic Top 10.

LLM06 primary — the finding's main OWASP categoryASI10 secondary — a category it also touches

CRITICAL PRAX-2026-08-12-001 In non-interactive, non-gateway runs every non-hardline dangerous command and all execute_code scripts auto-approve, in the same contexts where untrusted web and MCP content reaches the model.
Policy Rule — R-14, R-21 (Worker Remit):
"Code-execution and MCP-subprocess tools operating on untrusted-influenced input MUST additionally be operator-gated or run only under that OS-level isolation posture. / Destructive or irreversible shell and file operations (deletion, overwrite, bulk or recursive changes) MUST require operator approval before execution, in every execution context and regardless of interactive vs. unattended mode."
hermes-agent/tools/approval.py:1206 — lines 1206-1223 — "if not is_cli and not is_gateway and not is_ask" returns {"approved": True} for every non-hardline dangerous command outside a cron-deny session hermes-agent/tools/approval.py:1518 — lines 1465-1519 — check_execute_code_guard's own docstring records the hole: "in a purely local non-interactive non-gateway session ... this returns approved" hermes-agent/agent/tool_dispatch_helpers.py:336 — make_tool_result_message wraps web_extract/web_search/browser_*/mcp_* results in an advisory delimiter — the only thing standing between poisoned external content and the auto-approved exec path
High confidence
Recommended Action
  • In `tools/approval.py`, replace the non-interactive fall-through at lines 1206-1223 and 1518-1519 with a fail-closed default: deny dangerous commands and `execute_code` when no approval surface is attached, and require an explicit opt-in setting (mirroring `approvals.cron_mode: approve`) for operators who genuinely want unattended execution.
  • Log every such denial at WARNING with the pattern key, so headless runs that need an allowance are discoverable from `agent.log` rather than silently executing.
HIGH PRAX-2026-08-12-002 The desktop first-run and repair install pipes a remote shell script straight into an interpreter with no checksum, signature or pinned revision.
Policy Rule — R-24 (Worker Remit):
"The desktop self-update and first-run install path MUST NOT fetch or install agent runtime code from an unverified or unauthenticated source; installers and runtime updates MUST be integrity-verified before they replace the running app or backend."
hermes-desktop/src/main/installer.ts:898 — "curl -fsSL https://raw.githubusercontent.com/NousResearch/hermes-agent/main/scripts/install.sh | bash -s -- --skip-setup" spawned via bash -c with no hash check hermes-desktop/src/main/installer.ts:1001 — lines 1001-1013 — Windows wrapper fetches install.ps1 from the same branch URL, writes it to a temp file and invokes it; the only integrity step is a UTF-8 BOM rewrite
High confidence
Recommended Action
  • Pin the install script to a tagged release URL and verify a published SHA-256 before execution in both `runUnixInstall` and the Windows wrapper in `src/main/installer.ts`.
  • Ship the expected digest with the desktop build so verification does not depend on a second fetch from the same origin.
HIGH PRAX-2026-08-12-003 Four gateway adapters opt out of the central default-deny and default their own DM policy to open, so an enabled but unconfigured adapter dispatches work for any sender.
Policy Rule — R-05, R-09, R-23, R-26 (Worker Remit):
"MUST refuse to dispatch work, relay output, or resolve approvals until an operator-configured allowlist is set. MUST NOT fail open when no allowlist is configured. / Trusted People / Accounts / Agent work MUST NOT be dispatched, output relayed, or approvals resolved for any caller outside the configured authorization set. / On an enabled network-exposed adapter with no caller allowlist, the response MUST be fail-closed — refuse to serve rather than dispatch."
hermes-agent/gateway/run.py:6755 — lines 6747-6758 — with no allowlist configured, "if self._adapter_enforces_own_access_policy(source.platform): return True" runs before the default-deny return hermes-agent/gateway/platforms/wecom.py:850 — lines 850-861 — enforces_own_access_policy returns True while _is_dm_allowed returns True for any sender unless dm_policy is "disabled" or "allowlist"; the default at :163 is "open"
High confidence
Recommended Action
  • Make the adapter-owned policy path deny by default: in `gateway/platforms/wecom.py`, `weixin.py`, `qqbot/adapter.py` and `yuanbao.py`, change the `dm_policy` / `group_policy` default from `"open"` to `"allowlist"` so an unconfigured adapter serves nobody.
  • Alternatively gate the `enforces_own_access_policy` shortcut in `gateway/run.py:6755` on the adapter reporting that it actually holds a non-empty allowlist.
HIGH PRAX-2026-08-12-004 Official desktop builds send usage telemetry to a third-party endpoint by default, with consent inferred from the presence of a build key rather than an operator decision.
Policy Rule — R-10, R-19 (Worker Remit):
"Any outbound destination reached in code but absent from the operator's configured/allowlisted set is a trust expansion; under egress isolation, outbound connections MUST be limited to the allowlisted hosts. / Outbound telemetry and usage attribution are opt-in and off by default; no telemetry or usage data may egress unless the operator has explicitly enabled it."
hermes-desktop/src/renderer/src/utils/analytics.ts:14 — lines 9-19 — "if (stored === null) return hasKey; // First run: enabled if key exists" makes telemetry opt-out, not opt-in hermes-desktop/src/renderer/src/utils/analytics.ts:5 — POSTHOG_HOST defaults to https://eu.i.posthog.com — an outbound destination outside the operator's configured provider and platform set
High confidence
Recommended Action
  • In `src/renderer/src/utils/analytics.ts`, change the first-run branch to return `false` so telemetry stays off until the operator opts in, and surface the choice in the onboarding flow.
  • Record the operator's decision in the profile configuration rather than renderer `localStorage`, so a profile reset does not silently re-enable capture.
HIGH PRAX-2026-08-12-005 With approvals.mode set to smart, an auxiliary LLM resolves the destructive-command decision and its APPROVE also grants standing session approval for that pattern.
Policy Rule — R-25 (Worker Remit):
"On a destructive-command match, the response MUST be halt-pending-approval — pause and prompt the operator, never autonomous execution (the runtime enforcement of the destructive-operation approval gate)."
hermes-agent/tools/approval.py:878 — lines 878-922 — _smart_approve builds a prompt from the command text and maps a single-word LLM answer to approve / deny / escalate hermes-agent/tools/approval.py:1274 — lines 1270-1281 — on an LLM "approve" the handler calls approve_session for every warning key, so the pattern is silently pre-approved for the remainder of the session
High confidence
Recommended Action
  • In `tools/approval.py`, stop calling `approve_session()` on a smart-mode approval so the decision is one-shot rather than standing for the session.
  • Restrict smart mode to escalating or denying — never auto-approving — or gate it behind a setting that names the trade-off, and log every smart approval at WARNING with the command and verdict.
HIGH PRAX-2026-08-12-006 Nothing in code binds an enabled untrusted-ingress surface to a sandboxed terminal backend; the pairing is left entirely to operator discipline.
Policy Rule — R-13 (Worker Remit):
"the default local (host) terminal backend is authorized only for trusted content; any untrusted input surface MUST run under a sandboxed terminal backend (Docker/Modal/Daytona) or whole-process wrapping"
hermes-agent/tools/approval.py:1171 — lines 1170-1172 — guards are skipped wholesale for docker/singularity/modal/daytona, confirming the sandboxed backends are the intended containment, with no corresponding check that untrusted surfaces require one hermes-agent/SECURITY.md:116 — lines 116-119 — "Operators running the default local backend with untrusted input surfaces ... are operating outside the supported security posture" — the constraint is documented, not enforced
Medium confidence
Recommended Action
  • Add a startup interlock in the gateway launch path that refuses to enable an untrusted-ingress adapter (messaging, email, webhook) while `TERMINAL_ENV` resolves to `local`, overridable only by an explicit named setting.
  • Surface the resolved backend and the enabled ingress surfaces together in `hermes doctor` output so the mismatch is visible before it matters.
MEDIUM PRAX-2026-08-12-007 Untrusted-content labeling covers an enumerated list of four tool names with a 32-character floor, so file reads, code-execution output and session search reach the model unlabeled.
Policy Rule — R-01 (Worker Remit):
"The agent MUST NOT act on instructions embedded in ingested content — web pages, emails, inbound messages, file contents, tool results, or MCP server responses — as though they were operator commands."
hermes-agent/agent/tool_dispatch_helpers.py:351 — lines 351-361 — _UNTRUSTED_TOOL_NAMES holds only web_extract and web_search, prefixes only browser_ and mcp_, and _UNTRUSTED_WRAP_MIN_CHARS is 32 hermes-agent/tools/threat_patterns.py:4 — module docstring names tool_dispatch_helpers as a consumer of the pattern library, but the wrapper performs no scan — only prompt_builder.py and memory_tool.py call scan_for_threats
High confidence
Recommended Action
  • In `agent/tool_dispatch_helpers.py`, invert the list: wrap every tool result by default and maintain a small allowlist of first-party tools whose output is genuinely agent-internal.
  • Remove the 32-character floor or lower it to zero for the covered tools — a short result is exactly where a terse injected directive fits.
MEDIUM PRAX-2026-08-12-008 Skills the agent writes for itself bypass Skills Guard entirely because the gate that would scan them is off by default.
Policy Rule — R-11 (Worker Remit):
"Third-party MCP servers, skills, and plugins — but each MUST be reviewed and approved by the operator before install or activation; none may be auto-trusted on the strength of its own description alone."
hermes-agent/tools/skills_guard.py:55 — lines 55-59 — "agent-created" policy is ("allow","allow","ask") but the comment notes the gate "only runs when skills.guard_agent_created is enabled (off by default)" hermes-agent/tools/skills_guard.py:44 — TRUSTED_REPOS and INSTALL_POLICY show externally-sourced skills are always scanned, isolating the gap to the self-authoring path
High confidence
Recommended Action
  • Default `skills.guard_agent_created` to enabled in the config schema so agent-authored skill content is scanned on the same terms as community content.
  • Record every agent-created skill write to the install audit log with its scan verdict, so the self-authoring loop is reviewable after the fact.
MEDIUM PRAX-2026-08-12-009 There is no component inventory anywhere in the tree, and the one scanner that would find known-vulnerable pinned dependencies is configured never to fail.
hermes-agent/.github/workflows/osv-scanner.yml:67 — "fail-on-vuln: false" — the header comment confirms the choice is deliberate, so known CVEs in pinned versions never gate a merge or release hermes-agent — no *.cdx.json, *.spdx*, sbom*, aibom* or mlbom* file exists in either source root, and no drift check consumes one
High confidence
Recommended Action
  • Generate a CycloneDX SBOM for both roots as a release artifact (`uv`/`cyclonedx-py` for Python, `cyclonedx-npm` for the desktop) and attach it to each published build.
  • Keep `fail-on-vuln: false` if deliberate, but add a severity threshold that fails the release workflow specifically, so shipping is gated even when day-to-day merges are not.
MEDIUM PRAX-2026-08-12-010 The three primary logs are free-form text lines rather than structured records, and nothing exports or alerts beyond the local host.
Policy Rule — R-29 (Worker Remit):
"Tool invocations, approval decisions, and session lifecycle events MUST be recorded to durable, structured logs (`agent.log` / `gateway.log`; `desktop.log` for the desktop layer) sufficient to reconstruct what the agent did."
hermes-agent/hermes_logging.py:45 — lines 45-47 — _LOG_FORMAT is "%(asctime)s %(levelname)s%(session_tag)s %(name)s: %(message)s", a prose line rather than a structured record, for agent.log, errors.log and gateway.log hermes-agent/hermes_cli/dashboard_auth/audit.py:1 — the one structured log in the tree — "one JSON object per line" — covers dashboard auth events only, not tool invocations or approval decisions
High confidence
Recommended Action
  • Add a JSON-lines handler alongside the existing text handlers in `hermes_logging.setup_logging()` and emit tool invocations, approval decisions and session lifecycle events through it with stable field names.
  • Ship an optional OTLP or file-tail export path so an operator can route those events off-host, which nothing in the tree currently supports.
What's Working Well

Controls and behaviors that are correctly implemented and verified during this scan. These represent areas where the agent's implementation aligns with its stated policy and security best practices.

Unbypassable hardline command floor

A fixed set of catastrophic commands (rm -rf /, mkfs, dd to a raw device, shutdown, fork bomb, kill -1) is blocked before the yolo bypass, the approval-mode check and the cron path, so no session-level setting can defeat it.

tools/approval.py:1174-1192

YOLO mode frozen at import to block in-process escalation

HERMES_YOLO_MODE is read once at module import with an explicit comment that re-reading os.environ would let any skill inside the process disable every approval check, closing a prompt-injection escalation path.

tools/approval.py:26-29

API server fails closed without authentication

connect() refuses to start the API server at all when API_SERVER_KEY is unset, including on loopback binds, and refuses to start with a placeholder-strength key when the bind address is network-accessible.

gateway/platforms/api_server.py:4142-4168

Default-deny caller authorization in the gateway

_is_user_authorized() resolves per-platform allowlists, chat allowlists, the pairing store and the global allowlist, and returns deny when none is configured unless an explicit allow-all flag is set.

gateway/run.py:6747-6758

Credential scrubbing that skills and config cannot override

Provider credentials are stripped from shell, MCP and code-execution child environments, and both the skill-declared and config.yaml passthrough paths refuse to register anything on the provider blocklist — the shipped fix for GHSA-rhgp-j443-p4rf.

tools/env_passthrough.py:86-140

Injection-blocking on context files and agent memory

Context files matching the shared threat-pattern set are replaced with a placeholder before they can enter the system prompt, and memory entries are sanitized on load with a drift check that refuses writes when the file changed underneath the agent.

agent/prompt_builder.py:43-59

Electron main process hardened and asserted by tests

The renderer runs with nodeIntegration off, contextIsolation and sandbox on, navigation and window-open are allowlisted, attached webviews have their preload stripped — and electron-security.test.ts asserts these invariants against the source.

hermes-desktop/src/main/security.ts:53-77

Malware check before every MCP server launch

npx and uvx MCP server packages are queried against the OSV API for MAL-* malware advisories and blocked on a hit before the subprocess is spawned.

tools/osv_check.py:26-60

Exact dependency pinning with a documented incident rationale

Every direct Python dependency is pinned to an exact version with uv.lock regenerated alongside, and the policy comment names the PyPI worm incident that drove the change away from ranges.

hermes-agent/pyproject.toml:13-25

Security advisories traced to fixes in-tree

Release notes link GHSA-rhgp-j443-p4rf and GHSA-5qr3-c538-wm9j to the pull requests that fixed them, credit the reporter, and the advisory ID is cited in the fixing code itself.

hermes-agent/RELEASE_v0.15.0.md:435-437
Discovered Log Files

Log files found in the agent's workspace during this scan. Reviewing these files provides runtime evidence to complement the static analysis above.

Path Source Content Type Purpose Last Modified Status
~/.hermes/logs/agent.log hermes_logging.setup_logging (all agent/tool/session loggers) plaintext rotating log, timestamp + level + session tag + logger + message Main activity log at INFO+, including every tool completion with duration and every auto-approval warning unknown Inferred
~/.hermes/logs/errors.log hermes_logging.setup_logging (WARNING+ handler) plaintext rotating log Triage log for warnings and errors, including hardline and sudo-guard blocks unknown Inferred
~/.hermes/logs/gateway.log hermes_logging.setup_logging with mode=gateway, filtered to gateway.* loggers plaintext rotating log Gateway-only events — platform adapters, session management, slash commands, delivery unknown Inferred
$HERMES_HOME/logs/dashboard-auth.log hermes_cli/dashboard_auth/audit.py structured JSON lines with a token-field redaction denylist Dashboard authentication events — login, logout, refresh, revoke, session-verify failure, websocket ticket mint and reject unknown Inferred
$HERMES_HOME/logs/mcp-stderr.log tools/mcp_tool.py shared stderr file handle for stdio MCP subprocesses plaintext subprocess stderr capture Keeps MCP server stderr off the parent process stream and on disk for diagnosis unknown Inferred
$HERMES_HOME/logs/desktop.log hermes-desktop main process and updater-log.ts plaintext log Desktop app lifecycle and auto-updater events unknown Inferred
OWASP LLM Top 10 (2026) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

LLM05 Data and Model Poisoning
No findings
LLM06 Unbounded Consumption
No findings
LLM07 Misinformation
No findings
LLM08 Hidden Context Exposure
No findings
LLM09 Vector and Embedding Weaknesses
No findings
LLM10 Improper Output Handling
No findings
OWASP Agentic Top 10 (2026) Coverage

Each card represents one category and shows the top 3 findings. All items in the Findings section.

RAISE Maturity Posture

Overall maturity assessment across the six categories of the RAISE framework. This is a maturity model, not a school grade: a score of 3 / 5 means Established, not 60 percent. Most production AI agents today score between Ad hoc (1) and Established (3). See the full RAISE framework reference for the complete scale and scoring.

2.60 / 5.0
Weighted Maturity Score · Partial
Partial. Hermes has real, operative, code-level controls in every RAISE category and a written security policy that names its one load-bearing boundary and enumerates what it does not defend — which is why nothing here scores at the bottom of the scale. What holds the overall number down is concentrated in the two categories that carry the model's own inputs and actions: external web, browser and MCP content reaches the context with provenance labeling but no validation, and the approval gate that would bound what the model does with that content does not run in headless contexts. Supply chain, adversarial testing practice and monitoring are all established and actively maintained, with the gaps being an absent component inventory and the absence of any telemetry export or alerting beyond local log files.
Limit Your Domain
3/ 5
Confidence: Medium  |  Weight: 15%  |  Weighted: 0.45
Capability scoping is code-level rather than prompt-level — toolsets.py composes a fixed per-surface tool set and toolset_distributions.py scopes which surface receives which tools — and the observed runtime inventory matches the remit's Known Good Baseline closure with no capability found outside it. Scored 3 rather than 2 because the breadth is authorized by the remit, not drift; the accepted gap is that there is no subject-matter gate at all, only capability composition.
Balance Your Knowledge Base
2/ 5
Confidence: High  |  Weight: 15%  |  Weighted: 0.30
The dominant untrusted path — poisoned web, browser and MCP content flowing back as tool results — is covered only by a code-inserted instruction telling the model to treat the block as data (agent/tool_dispatch_helpers.py:389-397), with no content validation on tool results at all, which caps this category at 2 under the dominant-path ladder. Real deterministic controls exist off that path and keep it above 1: context files are blocked outright on injection match in agent/prompt_builder.py:43-59, memory entries are sanitized on load in tools/memory_tool.py:163-210, and tools/url_safety.py blocks private-range and cloud-metadata fetches by default.
Implement Zero Trust
2/ 5
Confidence: High  |  Weight: 25%  |  Weighted: 0.50
External live data reaches the model's context without sanitization and, in non-interactive non-gateway runs, tools/approval.py:1206-1223 and :1518-1519 auto-approve every non-hardline dangerous command and all execute_code scripts — the exact "every execution context" case the remit names. It stays at 2 rather than 1 because the controls that do run are genuinely operative and non-optional: the hardline and sudo-stdin floors that no session setting can bypass, an API server that refuses to start without a key, gateway default-deny authorization, and a credential-scrub blocklist that skills and config.yaml cannot override.
Manage Your Supply Chain
3/ 5
Confidence: High  |  Weight: 15%  |  Weighted: 0.45
Every direct Python dependency is exact-pinned with uv.lock and a written rationale naming the incident that drove the policy, a supply-chain scanner and a dependency-ceiling check both exit non-zero to block merges, OSV-Scanner runs on lockfile PRs and weekly, and every npx/uvx MCP server launch is checked against OSV for MAL-* malware advisories. Scored 3 rather than 4 on two committed gaps: there is no SBOM or component inventory anywhere in the tree (M10 none) and osv-scanner.yml:67 sets fail-on-vuln:false, so known CVEs in pinned versions never gate.
Build an AI Red Team
3/ 5
Confidence: High  |  Weight: 15%  |  Weighted: 0.45
M9 records a closed feedback loop — RELEASE_v0.15.0.md links GHSA-rhgp-j443-p4rf and GHSA-5qr3-c538-wm9j to the PRs that fixed them, the fix is visible in tools/env_passthrough.py with a regression test beside it — and M1 found 106 security-named test files that exercise the project's own defences, M6 confirms they are command-invocable, M7 runs them on push and PR, and M8 records a merge-blocking supply-chain gate. Held at 3 rather than 4 by M2, M3 and M5 all returning none: no adversarial corpus, no red-team tooling, and no dated exercise reports.
Monitor Continuously
3/ 5
Confidence: High  |  Weight: 15%  |  Weighted: 0.45
Logging is durable and action-level — rotating agent.log, errors.log and gateway.log all through a RedactingFormatter with per-session correlation tags, every tool completion logged with name and duration, a queryable session database recording role, tool name, tool calls and timestamps per message, and a JSON-lines dashboard-auth audit log with field redaction. Held at 3 by M12 returning none — no OpenTelemetry, OTLP, Splunk, Datadog, Elastic or CloudWatch configuration, no alert rules and no dashboards-as-code — so nothing leaves the host, and by the three primary logs being free-form text rather than structured records.

Maturity Scoring Rubric

Every score above is based on this scale. A score is a snapshot of observable posture — not a verdict on the people or team behind the system.

Score Label Meaning
5 Exemplary Best-in-class; automated, continuously tested, reference quality. Rarely achieved in shipping systems.
4 Strong Comprehensive controls, active management, minor gaps. Production-ready.
3 Established Documented controls consistently applied; known gaps accepted. A respectable baseline.
2 Partial Some controls exist but coverage is incomplete; key gaps remain.
1 Ad hoc Informal or inconsistent measures; relies on individual judgment.
0 Absent No evidence this category is addressed at all.
Weighting: the weighted overall above is the sum of each category's score × weight (the per-category weights are shown on each card). Zero Trust carries double weight by design; see the RAISE framework reference for the rationale.