mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-29 06:31:32 +00:00
fix(file-safety): write-deny pairing/ directory to prevent approved-list injection
The gateway pairing directory (~/.hermes/pairing/) stores per-platform access-control files (telegram-approved.json, discord-approved.json, etc.). A prompt-injected agent using write_file could add arbitrary user IDs to an approved file, granting persistent gateway access without going through the pairing code flow — the same threat class that motivated protecting webhook_subscriptions.json (#14157). The pairing directory was not included in the original control-plane protection because it postdates PR #14157. PR #30383 introduced the hashed-pending schema and made the approved files the sole source of truth for gateway access, raising the security sensitivity of the directory. Apply the same mcp-tokens pattern: block writes to pairing/ and any path within it, under both the active hermes_home and the root path (for profile-mode parity with the fix in #30382). Regression tests verify denial for pairing/telegram-approved.json, pairing/discord-pending.json, and the directory itself, in both normal and profile-mode layouts.
This commit is contained in:
parent
6c44d537cc
commit
4f4e337c47
2 changed files with 34 additions and 1 deletions
|
|
@ -127,6 +127,12 @@ def is_write_denied(path: str) -> bool:
|
|||
return True
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
pairing_real = os.path.realpath(os.path.join(base_real, "pairing"))
|
||||
if resolved == pairing_real or resolved.startswith(pairing_real + os.sep):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
safe_root = get_safe_write_root()
|
||||
if safe_root and not (resolved == safe_root or resolved.startswith(safe_root + os.sep)):
|
||||
|
|
|
|||
|
|
@ -68,10 +68,14 @@ class TestIsWriteDenied:
|
|||
"webhook_subscriptions.json",
|
||||
"mcp-tokens/token1.json",
|
||||
"mcp-tokens/subdir/token2.json",
|
||||
"pairing/telegram-approved.json",
|
||||
"pairing/discord-approved.json",
|
||||
"pairing/telegram-pending.json",
|
||||
"pairing",
|
||||
],
|
||||
)
|
||||
def test_hermes_control_files_and_mcp_tokens_denied(self, path):
|
||||
"""Hermes control files and mcp-tokens entries must be write-denied."""
|
||||
"""Hermes control files and mcp-tokens/pairing entries must be write-denied."""
|
||||
from hermes_constants import get_hermes_home
|
||||
hermes_home = get_hermes_home()
|
||||
full_path = str(hermes_home / path)
|
||||
|
|
@ -140,6 +144,29 @@ class TestIsWriteDenied:
|
|||
# The directory itself must also be denied (not just files inside)
|
||||
assert _is_write_denied(str(root / "mcp-tokens")) is True
|
||||
|
||||
def test_pairing_dir_denied(self, tmp_path, monkeypatch):
|
||||
"""Regression: pairing/ must be write-denied under both profile and root.
|
||||
|
||||
PR #30383 introduced ~/.hermes/pairing/{platform}-approved.json as the
|
||||
gateway access-control list. Without this block, a prompt-injected agent
|
||||
can write arbitrary user IDs into an approved file, granting persistent
|
||||
gateway access without going through the pairing code flow — the same
|
||||
threat class that motivated protecting webhook_subscriptions.json.
|
||||
"""
|
||||
root = tmp_path / "hermes"
|
||||
profile = root / "profiles" / "coder"
|
||||
profile.mkdir(parents=True)
|
||||
monkeypatch.setenv("HERMES_HOME", str(profile))
|
||||
|
||||
# Active profile pairing entries
|
||||
assert _is_write_denied(str(profile / "pairing" / "telegram-approved.json")) is True
|
||||
assert _is_write_denied(str(profile / "pairing" / "discord-pending.json")) is True
|
||||
# The directory itself
|
||||
assert _is_write_denied(str(profile / "pairing")) is True
|
||||
# Root pairing entries (profile mode — same shape as mcp-tokens gap)
|
||||
assert _is_write_denied(str(root / "pairing" / "telegram-approved.json")) is True
|
||||
assert _is_write_denied(str(root / "pairing")) is True
|
||||
|
||||
|
||||
|
||||
# =========================================================================
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue