Skip to content

fix(file-safety): write-deny pairing/ directory to prevent approved-list injection - #30412

Closed
AhmetArif0 wants to merge 1 commit into
NousResearch:mainfrom
AhmetArif0:fix/pairing-dir-write-deny
Closed

fix(file-safety): write-deny pairing/ directory to prevent approved-list injection#30412
AhmetArif0 wants to merge 1 commit into
NousResearch:mainfrom
AhmetArif0:fix/pairing-dir-write-deny

Conversation

@AhmetArif0

Copy link
Copy Markdown
Contributor

Problem

is_write_denied() in agent/file_safety.py blocks writes to known
control-plane files (auth.json, config.yaml, webhook_subscriptions.json,
mcp-tokens/) but does not block writes to the gateway pairing directory
(~/.hermes/pairing/).

The pairing directory stores per-platform access-control lists:

~/.hermes/pairing/
  telegram-approved.json   ← which user IDs may send commands
  telegram-pending.json    ← hashed codes awaiting approval
  telegram-rate_limits.json
  discord-approved.json
  ...

A prompt-injected agent using the write_file tool can write arbitrary user
IDs into telegram-approved.json, granting persistent gateway access without
going through the pairing code flow:

# Attacker-controlled prompt injection:
write_file(
    "~/.hermes/pairing/telegram-approved.json",
    '{"123456789": {"user_name": "attacker", "approved_at": 1716000000}}'
)

After this write, PairingStore.is_approved("telegram", "123456789") returns
True and the attacker can issue commands to the agent via Telegram with no
further authentication.

Root cause

PR #14157 protected webhook_subscriptions.json as a control-plane file.
PR #30383 introduced the pairing/ directory as the gateway access-control
store, but is_write_denied() was not updated to cover it — the same gap
shape as the profile-mode .env gap closed by PR #15981.

Verification (before fix):

is_write_denied('~/.hermes/pairing/telegram-approved.json') → False  ← bug
is_write_denied('~/.hermes/auth.json')                      → True   ✓
is_write_denied('~/.hermes/webhook_subscriptions.json')     → True   ✓

Fix

Apply the same mcp-tokens directory pattern to pairing/:

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

The check runs for both _hermes_home_path() and _hermes_root_path(),
matching the profile-mode widening applied to mcp-tokens/ in PR #30382.

Tests

Adds test_pairing_dir_denied to TestIsWriteDenied:

  • pairing/telegram-approved.json under active profile → denied
  • pairing/discord-pending.json under active profile → denied
  • pairing/ directory itself → denied
  • Same paths under hermes root (profile mode) → denied

Also extends the existing parametrized test with four pairing paths
(pairing/telegram-approved.json, pairing/discord-approved.json,
pairing/telegram-pending.json, pairing).

All 28 TestIsWriteDenied tests pass.

Checklist

  • Root cause at exact file/function/line (is_write_denied, agent/file_safety.py)
  • Symmetric with mcp-tokens protection pattern already in the same function
  • Covers both active profile (hermes_home) and root (hermes_root) paths
  • No behavior change for any path outside ~/.hermes/pairing/
  • Regression tests added for normal mode and profile mode
  • All 28 write-deny tests pass

…ist 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 (NousResearch#14157).

The pairing directory was not included in the original control-plane protection
because it postdates PR NousResearch#14157. PR NousResearch#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 NousResearch#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.
@alt-glitch alt-glitch added type/security Security vulnerability or hardening P0 Critical — data loss, security, crash loop comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery area/auth Authentication, OAuth, credential pools labels May 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/auth Authentication, OAuth, credential pools comp/agent Core agent runtime: loop, agent_init, prompt builder, context-compression, responses endpoint comp/gateway Gateway runner, session dispatch, delivery P0 Critical — data loss, security, crash loop type/security Security vulnerability or hardening

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants