mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-07 02:51:50 +00:00
fix(signal): skip reactions for unauthorized senders
The on_processing_start hook fired a reaction emoji (👀) on every
inbound Signal message before run.py's _is_user_authorized check.
This meant contacts not in SIGNAL_ALLOWED_USERS would see the bot
react to their messages even though Hermes silently dropped them —
leaking the presence of the bot and causing confusing UX.
Two changes to gateway/platforms/signal.py:
1. Read SIGNAL_ALLOWED_USERS into self.dm_allow_from in __init__
(mirrors the group_allow_from pattern already in place).
2. Add _reactions_enabled(event) — two-gate check:
- SIGNAL_REACTIONS=false/0/no disables reactions globally
- If SIGNAL_ALLOWED_USERS is set, only react to senders in
the allowlist (skips unauthorized contacts)
Both on_processing_start and on_processing_complete now call this
guard before sending any reaction.
Telegram already has an equivalent _reactions_enabled() guard
(controlled by TELEGRAM_REACTIONS). This brings Signal to parity.
This commit is contained in:
parent
e89376d66f
commit
8a4fe80f8d
1 changed files with 31 additions and 0 deletions
|
|
@ -192,6 +192,15 @@ class SignalAdapter(BasePlatformAdapter):
|
||||||
group_allowed_str = os.getenv("SIGNAL_GROUP_ALLOWED_USERS", "")
|
group_allowed_str = os.getenv("SIGNAL_GROUP_ALLOWED_USERS", "")
|
||||||
self.group_allow_from = set(_parse_comma_list(group_allowed_str))
|
self.group_allow_from = set(_parse_comma_list(group_allowed_str))
|
||||||
|
|
||||||
|
# DM allowlist — mirrors SIGNAL_ALLOWED_USERS checked by run.py.
|
||||||
|
# Stored here so the reaction hooks can skip unauthorized senders
|
||||||
|
# (reactions fire before run.py's auth gate, so without this check
|
||||||
|
# every inbound DM from any contact gets a 👀 reaction).
|
||||||
|
# "*" means all users allowed (open mode); empty means no restriction
|
||||||
|
# recorded at adapter level (run.py still enforces auth separately).
|
||||||
|
dm_allowed_str = os.getenv("SIGNAL_ALLOWED_USERS", "*")
|
||||||
|
self.dm_allow_from = set(_parse_comma_list(dm_allowed_str))
|
||||||
|
|
||||||
# HTTP client
|
# HTTP client
|
||||||
self.client: Optional[httpx.AsyncClient] = None
|
self.client: Optional[httpx.AsyncClient] = None
|
||||||
|
|
||||||
|
|
@ -1430,8 +1439,28 @@ class SignalAdapter(BasePlatformAdapter):
|
||||||
return None
|
return None
|
||||||
return (author, ts)
|
return (author, ts)
|
||||||
|
|
||||||
|
def _reactions_enabled(self, event: "MessageEvent" = None) -> bool:
|
||||||
|
"""Check if message reactions are enabled for this event.
|
||||||
|
|
||||||
|
Two gates:
|
||||||
|
1. SIGNAL_REACTIONS env var — set to false/0/no to disable globally.
|
||||||
|
2. DM allowlist — if SIGNAL_ALLOWED_USERS is set, only react to
|
||||||
|
messages from senders in that list. This prevents unauthorized
|
||||||
|
contacts from seeing the 👀 reaction (which fires before run.py's
|
||||||
|
auth gate and would otherwise reveal that a bot is listening).
|
||||||
|
"""
|
||||||
|
if os.getenv("SIGNAL_REACTIONS", "true").lower() in ("false", "0", "no"):
|
||||||
|
return False
|
||||||
|
if event is not None:
|
||||||
|
sender = getattr(getattr(event, "source", None), "user_id", None)
|
||||||
|
if sender and "*" not in self.dm_allow_from and sender not in self.dm_allow_from:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
async def on_processing_start(self, event: MessageEvent) -> None:
|
async def on_processing_start(self, event: MessageEvent) -> None:
|
||||||
"""React with 👀 when processing begins."""
|
"""React with 👀 when processing begins."""
|
||||||
|
if not self._reactions_enabled(event):
|
||||||
|
return
|
||||||
target = self._extract_reaction_target(event)
|
target = self._extract_reaction_target(event)
|
||||||
if target:
|
if target:
|
||||||
await self.send_reaction(event.source.chat_id, "👀", *target)
|
await self.send_reaction(event.source.chat_id, "👀", *target)
|
||||||
|
|
@ -1442,6 +1471,8 @@ class SignalAdapter(BasePlatformAdapter):
|
||||||
On CANCELLED we leave the 👀 in place — no terminal outcome means
|
On CANCELLED we leave the 👀 in place — no terminal outcome means
|
||||||
the reaction should keep reflecting "in progress" (matches Telegram).
|
the reaction should keep reflecting "in progress" (matches Telegram).
|
||||||
"""
|
"""
|
||||||
|
if not self._reactions_enabled(event):
|
||||||
|
return
|
||||||
if outcome == ProcessingOutcome.CANCELLED:
|
if outcome == ProcessingOutcome.CANCELLED:
|
||||||
return
|
return
|
||||||
target = self._extract_reaction_target(event)
|
target = self._extract_reaction_target(event)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue