fix(security): protect Hermes control-plane files from prompt injection (#14157)#30397
Merged
Conversation
Adds active-HERMES_HOME control-plane files to the write deny list: auth.json, config.yaml, webhook_subscriptions.json, and any path under mcp-tokens/. realpath() resolves before comparison so directory-traversal and symlink targets are normalised, preventing trivial deny-list bypass via ../ tricks. Without this, a prompt-injected agent could rewrite Hermes' own auth state or routing config via write_file / patch — without triggering the terminal dangerous-command approval — and persist attacker-controlled behaviour across sessions. Fixes #14072
PR #14157 added control-plane write-deny against the ACTIVE HERMES_HOME, which is fine in non-profile mode but leaves a gap once a profile is active: HERMES_HOME points at <root>/profiles/<name>, so the global <root>/auth.json + <root>/config.yaml + <root>/webhook_subscriptions.json + <root>/mcp-tokens/ remain writable. Same shape as the .env gap PR #15981 closed via _hermes_root_path(). Apply the same widening pattern here. The control-file/mcp-tokens check now iterates BOTH _hermes_home_path() and _hermes_root_path() (dedupes when they coincide in non-profile mode). Also tightens the mcp-tokens check from "startswith dir + os.sep" to "==dir OR startswith dir + os.sep" so writing the directory entry itself is blocked, not just files inside. Regression tests cover both protections in a real profile-mode layout (<tmp>/hermes/profiles/coder as HERMES_HOME, <tmp>/hermes as root).
Contributor
🔎 Lint report:
|
19 tasks
13 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Salvages @PratikRai0101's PR #14157.
write_fileandpatchcan no longer be used to rewrite Hermes' control-plane files (auth.json,config.yaml,webhook_subscriptions.json,mcp-tokens/) via prompt injection — the deny list catches them at both the active profile view AND the global root view.Root cause
is_write_denied()covered shell rc files,.ssh,.env, etc. but did not cover Hermes' own control plane. A prompt-injectedwrite_file(~/.hermes/auth.json, ...)succeeded silently — no terminal-approval prompt, no deny — letting the model rewrite OAuth state, providers, routing, or webhook handlers persistently across sessions.Changes
agent/file_safety.py(+23, contributor): adds activeHERMES_HOMEcontrol files +mcp-tokens/to the deny set. Usesrealpathso traversal (../) and symlinks resolve correctly.tests/tools/test_file_operations.py(+44, contributor): parameterized coverage — direct paths, traversal attempts, allowed standard paths.agent/file_safety.py(+27 -19, follow-up): widen to BOTH_hermes_home_path()AND_hermes_root_path(). Without this, profile mode (HERMES_HOME = <root>/profiles/<name>) left<root>/auth.json+<root>/config.yamlwritable — same shape as the.envgap PR write_file tool bypasses credential protection for global ~/.hermes/.env #15981 closed. Also tighten themcp-tokens/check fromstartswith(dir + sep)to== dir OR startswith(dir + sep)so writing the directory entry itself is blocked.tests/tools/test_file_operations.py(+36, follow-up): regression tests use a real profile-mode layout (<tmp>/hermes/profiles/coderas HERMES_HOME,<tmp>/hermesas root) and assert protection on both views.Stripped from the contributor diff
The original PR included ~50 lines of unrelated black-style formatter reflows in
test_file_operations.py— blank-line insertions, line-break tweaks on unchanged code. Stripped during salvage so the contributor commit reflects only the security work. Also stripped two cosmetic edits inagent/file_safety.py(an inserted blank line and one line-break onsafe_root).Validation
write_file(~/.hermes/auth.json)write_file(~/.hermes/mcp-tokens/gh.json)write_file(~/.hermes/dummy/../auth.json)write_file(~/.hermes/auth.json)write_file(~/.hermes/profiles/coder/auth.json)write_file(/tmp/anything.txt)tests/tools/test_file_operations.py— 69/69 pass (61 original + 3 contributor + 5 follow-up).tests/tools/test_write_deny.py(PR #15981 regression suite) — 20/20 pass (no overlap conflict).Attribution
@PratikRai0101's commit preserved with original authorship (date 2026-04-23) — rebase-merge keeps it visible in
git log. Follow-up widening commit attributed to Teknium. Closes #14072 + #14157 on merge.Infographic