The DM pairing system's brute-force protection can be bypassed because PairingStore.approve_code() does not check whether the platform is currently locked out before validating a pairing code.
Impact
After 5 failed approval attempts, a platform enters a 1-hour lockout (_is_locked_out() == True). However, an attacker who has obtained or guessed a pending valid pairing code can still call approve_code() successfully during the lockout period, gaining unauthorized access to the bot. This effectively nullifies the lockout mechanism.
Steps to Reproduce
from gateway.pairing import PairingStore, MAX_FAILED_ATTEMPTS
store = PairingStore()
valid_code = store.generate_code("telegram", "attacker", "Attacker")
# Trigger lockout with wrong codes
for _ in range(MAX_FAILED_ATTEMPTS):
result = store.approve_code("telegram", "WRONGCODE")
assert result is None
assert store._is_locked_out("telegram") is True # Platform is locked out
# But the valid code is still accepted!
result = store.approve_code("telegram", valid_code)
print(result) # {'user_id': 'attacker', 'user_name': 'Attacker'}
print(store.is_approved("telegram", "attacker")) # True
Expected Behavior
approve_code("telegram", valid_code) should return None while the platform is locked out, and the user must not be added to the approved list.
Actual Behavior
approve_code() succeeds and adds the user to the approved list even during lockout.
Root Cause
In gateway/pairing.py, the approve_code() method calls _cleanup_expired() and then immediately looks up the code in pending, but it never calls _is_locked_out(platform). The lockout check only exists in generate_code(), so it only blocks new code generation but not code approval.
Suggested Fix
Add a lockout guard at the beginning of approve_code() (after _cleanup_expired and before looking up the pending code):
with self._lock:
self._cleanup_expired(platform)
code = code.upper().strip()
# Prevent brute-force bypass during lockout
if self._is_locked_out(platform):
return None
pending = self._load_json(self._pending_path(platform))
...
Environment
- Repository:
NousResearch/hermes-agent
- File:
gateway/pairing.py
- Method:
PairingStore.approve_code()
- Commit tested:
main branch (latest as of 2026-04-15)
The DM pairing system's brute-force protection can be bypassed because
PairingStore.approve_code()does not check whether the platform is currently locked out before validating a pairing code.Impact
After 5 failed approval attempts, a platform enters a 1-hour lockout (
_is_locked_out() == True). However, an attacker who has obtained or guessed a pending valid pairing code can still callapprove_code()successfully during the lockout period, gaining unauthorized access to the bot. This effectively nullifies the lockout mechanism.Steps to Reproduce
Expected Behavior
approve_code("telegram", valid_code)should returnNonewhile the platform is locked out, and the user must not be added to the approved list.Actual Behavior
approve_code()succeeds and adds the user to the approved list even during lockout.Root Cause
In
gateway/pairing.py, theapprove_code()method calls_cleanup_expired()and then immediately looks up the code inpending, but it never calls_is_locked_out(platform). The lockout check only exists ingenerate_code(), so it only blocks new code generation but not code approval.Suggested Fix
Add a lockout guard at the beginning of
approve_code()(after_cleanup_expiredand before looking up the pending code):Environment
NousResearch/hermes-agentgateway/pairing.pyPairingStore.approve_code()mainbranch (latest as of 2026-04-15)