From b647aa5f47f26a3f62326fb3ad93cbfaf53fb4bd Mon Sep 17 00:00:00 2001 From: Kaloyan Tenchov Date: Tue, 19 May 2026 08:57:47 -0400 Subject: [PATCH] fix(signal): route denied DMs through _handle_message for pairing code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously _check_inbound_policy returned (False, chat_id) for DMs that failed the allowlist and the caller dropped them — so unapproved DM senders never saw a pairing code. Mirror Slack: when the policy gate denies a DM but dm.enabled is true, still call _handle_message(content="", is_dm=True) so BaseChannel can issue the pairing reply. Group denials stay a hard drop. Combined with the previous is_dm forwarding, unapproved DM senders now receive a pairing code through the standard flow. Addresses review comment on PR #3852. --- nanobot/channels/signal.py | 9 +++++++++ tests/channels/test_signal_channel.py | 9 +++++++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/nanobot/channels/signal.py b/nanobot/channels/signal.py index 01e2cd981..781a6bdd7 100644 --- a/nanobot/channels/signal.py +++ b/nanobot/channels/signal.py @@ -704,6 +704,15 @@ class SignalChannel(BaseChannel): timestamp=timestamp, ) if not allowed: + # Mirror Slack: let denied DMs reach _handle_message so the base + # class can reply with a pairing code. Group denials stay dropped. + if not is_group_message and self.config.dm.enabled: + await self._handle_message( + sender_id=sender_id, + chat_id=chat_id, + content="", + is_dm=True, + ) return content, media_paths = self._assemble_inbound_content( diff --git a/tests/channels/test_signal_channel.py b/tests/channels/test_signal_channel.py index 214117176..3a0565570 100644 --- a/tests/channels/test_signal_channel.py +++ b/tests/channels/test_signal_channel.py @@ -670,11 +670,16 @@ class TestHandleDataMessageDM: assert len(handled) == 1 @pytest.mark.asyncio - async def test_dm_allowlist_rejected(self): + async def test_dm_allowlist_rejected_triggers_pairing(self): + # Denied DM senders are routed to _handle_message with empty content + # and is_dm=True so BaseChannel issues a pairing code (mirrors Slack). ch, handled = self._make_dm_channel(policy="allowlist", allow_from=["+10000000001"]) params = _dm_envelope(source_number="+19995550002") await ch._handle_receive_notification(params) - assert handled == [] + assert len(handled) == 1 + assert handled[0]["content"] == "" + assert handled[0]["is_dm"] is True + assert handled[0]["chat_id"] == "+19995550002" @pytest.mark.asyncio async def test_dm_allowlist_matches_without_plus_prefix(self):