Skip to content

[security] Gateway attaches protected config.yaml via extract_local_files when file_mutation_verifier reports denied write #35584

@albertoMartinsen

Description

@albertoMartinsen

Bug Description

When the agent attempts to edit ~/.hermes/config.yaml (a protected credential file), the write is correctly denied by the file guard. However, the per-turn file_mutation_verifier appends the file path to the agent's final response (as a warning footer). The gateway's extract_local_files() then picks up the bare absolute path — ~/.hermes/config.yaml ends in .yaml, which is in MEDIA_DELIVERY_EXTS — and since the file exists on disk, it attaches it as a native file upload to the messaging platform.

This means API keys and other credentials from config.yaml can be silently exfiltrated through messaging channels (Telegram, Discord, Matrix, etc.) without the user's knowledge.

Steps to Reproduce

  1. Have a gateway-connected Hermes session (Matrix, Telegram, etc.)
  2. Request the agent to modify ~/.hermes/config.yaml (e.g., via patch)
  3. The write is denied (protected file), so the file_mutation_verifier adds the footer:
    ⚠️ File-mutation verifier: 1 file(s) were NOT modified...
    • /Users/.../.hermes/config.yaml — [patch] Write denied: '...' is a protected system/credential file.
    
  4. extract_local_files() in gateway/platforms/base.py:2641 matches the bare path, validates with os.path.isfile(), and attaches the full config.yaml

Root Cause

Two independent features interact dangerously:

  1. file_mutation_verifier (run_agent.py:2110) — prints denied file paths in the user-facing response as a footer
  2. extract_local_files() (gateway/platforms/base.py:2641) — scans response text for absolute/tilde paths ending in known extensions (.yaml, .yml, .json, etc. from MEDIA_DELIVERY_EXTS) and attaches them if the file exists

The extension allowlist in MEDIA_DELIVERY_EXTS (base.py:1160) was broadened in v0.15 (unifying the old separate lists from extract_media and extract_local_files), which added .yaml, .yml, .json, .xml and other config/data formats.

Expected Behavior

The extract_local_files() scanner should either:

  • Exclude known sensitive paths (config.yaml, .env, credentials, keys)
  • OR only match paths in a safe subset of directories (not ~/.hermes/)
  • OR the file_mutation_verifier should not expose protected paths in the user-facing footer (use a redacted/sanitized form)

Environment

  • Hermes Agent: v0.15.0 (commit b1a25404b)
  • OS: macOS 26.5
  • Gateway: Matrix (local)

Impact

High — While the Matrix setup here is local (no data leaves the machine), users on Telegram, Discord, Slack, or other cloud-connected platforms would have their API keys uploaded to those services' servers without any warning. The file is attached silently as part of the normal message delivery.

Relevant Code

  • gateway/platforms/base.py:1170.yaml and .yml in MEDIA_DELIVERY_EXTS
  • gateway/platforms/base.py:2641-2712extract_local_files() scans response text
  • run_agent.py:2110-2139_format_file_mutation_failure_footer() includes full paths
  • tools/file_operations.py:997-998 — the write denial that triggers the verifier

Metadata

Metadata

Assignees

No one assigned

    Labels

    P0Critical — data loss, security, crash looparea/configConfig system, migrations, profilescomp/agentCore agent loop, run_agent.py, prompt buildercomp/gatewayGateway runner, session dispatch, deliverytype/securitySecurity vulnerability or hardening

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions