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
- Have a gateway-connected Hermes session (Matrix, Telegram, etc.)
- Request the agent to modify
~/.hermes/config.yaml (e.g., via patch)
- 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.
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:
file_mutation_verifier (run_agent.py:2110) — prints denied file paths in the user-facing response as a footer
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-2712 — extract_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
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-turnfile_mutation_verifierappends the file path to the agent's final response (as a warning footer). The gateway'sextract_local_files()then picks up the bare absolute path —~/.hermes/config.yamlends in.yaml, which is inMEDIA_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
~/.hermes/config.yaml(e.g., viapatch)file_mutation_verifieradds the footer:extract_local_files()ingateway/platforms/base.py:2641matches the bare path, validates withos.path.isfile(), and attaches the full config.yamlRoot Cause
Two independent features interact dangerously:
file_mutation_verifier(run_agent.py:2110) — prints denied file paths in the user-facing response as a footerextract_local_files()(gateway/platforms/base.py:2641) — scans response text for absolute/tilde paths ending in known extensions (.yaml,.yml,.json, etc. fromMEDIA_DELIVERY_EXTS) and attaches them if the file existsThe extension allowlist in
MEDIA_DELIVERY_EXTS(base.py:1160) was broadened in v0.15 (unifying the old separate lists fromextract_mediaandextract_local_files), which added.yaml,.yml,.json,.xmland other config/data formats.Expected Behavior
The
extract_local_files()scanner should either:~/.hermes/)file_mutation_verifiershould not expose protected paths in the user-facing footer (use a redacted/sanitized form)Environment
b1a25404b)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—.yamland.ymlinMEDIA_DELIVERY_EXTSgateway/platforms/base.py:2641-2712—extract_local_files()scans response textrun_agent.py:2110-2139—_format_file_mutation_failure_footer()includes full pathstools/file_operations.py:997-998— the write denial that triggers the verifier