hermes - ✅(Solved) Fix Security: write_file/patch can modify ~/.hermes control-plane files (auth.json, config.yaml, webhook subscriptions) [1 pull requests, 1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
NousResearch/hermes-agent#14072Fetched 2026-04-23 07:46:56
View on GitHub
Comments
1
Participants
2
Timeline
8
Reactions
0
Author
Timeline (top)
labeled ×6commented ×1cross-referenced ×1

write_file and patch block a handful of sensitive paths, but the deny list does not cover Hermes control-plane files under HERMES_HOME such as ~/.hermes/auth.json, ~/.hermes/config.yaml, and ~/.hermes/webhook_subscriptions.json.

Because these tools are available without going through terminal approval, a prompt-injected model can persistently modify Hermes runtime/auth/configuration state directly through the file tools.

Root Cause

Because these tools are available without going through terminal approval, a prompt-injected model can persistently modify Hermes runtime/auth/configuration state directly through the file tools.

Fix Action

Fix / Workaround

write_file and patch block a handful of sensitive paths, but the deny list does not cover Hermes control-plane files under HERMES_HOME such as ~/.hermes/auth.json, ~/.hermes/config.yaml, and ~/.hermes/webhook_subscriptions.json.

  • tools/file_operations.py:45-79 — static write deny list only covers .ssh, .aws, shell rc files, .env, etc.
  • tools/file_operations.py:683-685write_file denies only _is_write_denied(path).
  • tools/file_operations.py:740-742patch_replace uses the same deny list.
  • tools/file_tools.py:541-556write_file_tool() relies on the same checks.
  • tools/file_tools.py:565-600patch_tool() relies on the same checks.

At minimum block writes/patches to:

PR fix notes

PR #14157: fix(security): protect Hermes control-plane files from prompt injection #14072

Description (problem / solution / changelog)

What does this PR do?

This PR resolves a P0 vulnerability where prompt-injected models could bypass terminal approval and use write_file or patch tools to persistently overwrite Hermes control-plane files.

It hardens the is_write_denied() logic to dynamically resolve the active HERMES_HOME and strictly block modifications to auth.json, config.yaml, webhook_subscriptions.json, and the mcp-tokens/ directory.

Related Issue

Fixes #14072

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 🔒 Security fix
  • 📝 Documentation update
  • ✅ Tests (adding or improving test coverage)
  • ♻️ Refactor (no behavior change)
  • 🎯 New skill (bundled or hub)

Changes Made

  • agent/file_safety.py: Updated is_write_denied() to enforce strict protection over HERMES_HOME control files. Implemented os.path.realpath resolution to categorically prevent directory traversal (e.g., ../) and symlink bypasses.
  • tests/tools/test_file_operations.py: Added comprehensive parameterized pytest coverage validating direct paths, traversal attempts, mcp-tokens/ subdirectories, and standard benign paths.

How to Test

  1. Run pytest tests/tools/test_file_operations.py::TestIsWriteDenied -v and verify all parameterized test cases pass.
  2. Launch an agent session and explicitly instruct it to use the write_file tool to overwrite ~/.hermes/config.yaml or ~/.hermes/dummy/../auth.json.
  3. Verify the tool handler rejects the operation with a write-denied error.

Checklist

Code

  • I've read the Contributing Guide
  • My commit messages follow Conventional Commits (fix(scope):, feat(scope):, etc.)
  • I searched for existing PRs to make sure this isn't a duplicate
  • My PR contains only changes related to this fix/feature (no unrelated commits)
  • I've run pytest tests/ -q and all tests pass
  • I've added tests for my changes (required for bug fixes, strongly encouraged for features)
  • I've tested on my platform: Arch Linux

Documentation & Housekeeping

  • I've updated relevant documentation (README, docs/, docstrings) — or N/A
  • I've updated cli-config.yaml.example if I added/changed config keys — or N/A
  • I've updated CONTRIBUTING.md or AGENTS.md if I changed architecture or workflows — or N/A
  • I've considered cross-platform impact (Windows, macOS) per the compatibility guide — or N/A
  • I've updated tool descriptions/schemas if I changed tool behavior — or N/A

Screenshots / Logs

Test Execution Output:

$ ./venv/bin/pytest tests/tools/ -k "file" --tb=short
================= 463 passed, 4 skipped, 19 warnings in 20.76s =================

## Changed files

- `agent/file_safety.py` (modified, +27/-1)
- `tests/tools/test_file_operations.py` (modified, +68/-4)
RAW_BUFFERClick to expand / collapse

Summary

write_file and patch block a handful of sensitive paths, but the deny list does not cover Hermes control-plane files under HERMES_HOME such as ~/.hermes/auth.json, ~/.hermes/config.yaml, and ~/.hermes/webhook_subscriptions.json.

Because these tools are available without going through terminal approval, a prompt-injected model can persistently modify Hermes runtime/auth/configuration state directly through the file tools.

Affected code

  • tools/file_operations.py:45-79 — static write deny list only covers .ssh, .aws, shell rc files, .env, etc.
  • tools/file_operations.py:683-685write_file denies only _is_write_denied(path).
  • tools/file_operations.py:740-742patch_replace uses the same deny list.
  • tools/file_tools.py:541-556write_file_tool() relies on the same checks.
  • tools/file_tools.py:565-600patch_tool() relies on the same checks.

Impact

A malicious prompt, compromised MCP/tool output, or indirect prompt injection can use file tools to silently modify Hermes trust-boundary files without triggering dangerous-command approval. Examples:

  • overwrite ~/.hermes/config.yaml to change providers, routing, startup behavior, or enabled features
  • modify ~/.hermes/auth.json / OAuth state to break or redirect future auth flows
  • alter ~/.hermes/webhook_subscriptions.json to persist attacker-controlled webhook behavior
  • tamper with other profile control files under HERMES_HOME

This creates a persistence/backdoor path that survives the current session and affects future runs.

Why this is distinct from generic "wide-open filesystem" concerns

The problem is not just that writes are broad by default — it is that Hermes' own security-sensitive control-plane files are not treated as protected, even though they directly govern future agent behavior and credentials.

Suggested fix

Treat Hermes control-plane files as protected by default in _is_write_denied().

At minimum block writes/patches to:

  • get_hermes_home() / "auth.json"
  • get_hermes_home() / "config.yaml"
  • get_hermes_home() / ".env" (already blocked; keep it)
  • get_hermes_home() / "webhook_subscriptions.json"
  • get_hermes_home() / "mcp-tokens"/**
  • Hermes hook/plugin registry paths and any executable hook directories

A stronger long-term fix would be to make file writes workspace-scoped by default and require explicit approval for writes under HERMES_HOME.

Severity

High — persistent agent reconfiguration / future-session compromise via prompt-driven file writes.

extent analysis

TL;DR

Update the _is_write_denied() function to block writes to sensitive Hermes control-plane files under HERMES_HOME.

Guidance

  • Review the tools/file_operations.py file, specifically lines 45-79, to understand the current deny list implementation.
  • Modify the _is_write_denied() function to include the suggested paths, such as get_hermes_home() / "auth.json" and get_hermes_home() / "config.yaml", to block writes to these sensitive files.
  • Consider implementing a stronger long-term fix by making file writes workspace-scoped by default and requiring explicit approval for writes under HERMES_HOME.
  • Test the updated _is_write_denied() function to ensure it correctly blocks writes to the specified paths.

Example

def _is_write_denied(path):
    # existing deny list implementation
    deny_list = [".ssh", ".aws", ".env"]
    # add Hermes control-plane files to the deny list
    hermes_home = get_hermes_home()
    deny_list.extend([
        hermes_home / "auth.json",
        hermes_home / "config.yaml",
        hermes_home / "webhook_subscriptions.json",
    ])
    # check if the path is in the deny list
    return any(path.startswith(deny_list_path) for deny_list_path in deny_list)

Notes

The suggested fix focuses on updating the _is_write_denied() function to block writes to sensitive Hermes control-plane files. However, a more comprehensive solution would involve making file writes workspace-scoped by default and requiring explicit approval for writes under HERMES_HOME.

Recommendation

Apply the workaround by updating the _is_write_denied() function to block writes to sensitive Hermes control-plane files, as this provides an immediate fix to the security vulnerability. A more robust solution can be implemented in the long term.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING