feat(gateway): bring Signal groups to Telegram parity (authz, mention, owner detection) - #53348
feat(gateway): bring Signal groups to Telegram parity (authz, mention, owner detection)#53348sdugoten wants to merge 5 commits into
Conversation
d07b698 to
45668ef
Compare
|
45668ef to
2de3511
Compare
2de3511 to
0ed2f61
Compare
…, owner detection) Signal group chats lagged Telegram/WhatsApp. This brings parity: - authz_mixin: authorize Signal group members via SIGNAL_GROUP_ALLOWED_USERS (same env signal.py already honors); add a generic _is_owner() fallback. - signal.py: reply-to-bot and /slash bypass require_mention; owner detection resolves group UUID->phone and sets source.is_owner at intake. - run.py/session.py: surface owner status WITHOUT changing upstream's [name] sender prefix. A **Owner:** context line covers single-user sessions; for cache-shared group sessions (where the context prompt is sender-agnostic) an additive [SYSTEM: sender NAME is the owner] marker is prepended ONLY for the owner, so guest lines stay byte-identical to upstream. Supersedes NousResearch#44706. Refs NousResearch#7269.
0ed2f61 to
31e76ec
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for the broader Signal-group pass. The underlying authorization gap is real, but two parts need correction before this can be salvaged.
Problems
- The added Signal map entry still compares
source.chat_idwith the allowlist. Signal emitschat_id="group:<id>"atgateway/platforms/signal.py:607, while its allowlist contains raw IDs (:593-603) and the raw value is available aschat_id_alt(:709). Thus explicit configured groups remain unauthorized; only*works. - The new reply-to-bot gate recognizes only quote authors equal to the account phone. Current Signal logic already resolves own replies through timestamps and UUID/phone mappings at
gateway/platforms/signal.py:790-807; the new branch should use that path.
Suggested changes
- Match
chat_id_alt(or normalizegroup:) and add authorization-path tests for explicit IDs, wildcard, and deny cases. - Reuse
_quote_references_own_message()and add UUID/timestamp reply tests. The current added tests cover only marker formatting.
Automated hermes-sweeper review.
| chat_allowlist_env = { | ||
| Platform.TELEGRAM: "TELEGRAM_GROUP_ALLOWED_CHATS", | ||
| Platform.QQBOT: "QQ_GROUP_ALLOWED_USERS", | ||
| Platform.SIGNAL: "SIGNAL_GROUP_ALLOWED_USERS", |
There was a problem hiding this comment.
Signal's configured group IDs are raw, but the adapter sets source.chat_id to group:<id> and stores the raw ID in chat_id_alt. The existing comparison will therefore still reject every explicitly configured Signal group; match chat_id_alt or normalize the prefix here.
| # treat a quote of a message the bot itself sent as "addressed to me". | ||
| _q = data_message.get("quote") or {} | ||
| _q_author = _q.get("author") or _q.get("authorNumber") or _q.get("authorUuid") | ||
| replied_to_bot = bool(_q_author) and _q_author in { |
There was a problem hiding this comment.
Please use the adapter's existing _quote_references_own_message() path here. It also recognizes our sent-message timestamps and account UUID/UUID-to-phone mappings, while this comparison only accepts a phone-form quote author.
…tch + robust reply-to-bot Two correctness fixes from the hermes-sweeper review: 1. Group chat-allowlist matching (gateway/authz_mixin.py) Signal emits chat_id="group:<id>" while SIGNAL_GROUP_ALLOWED_USERS holds the raw <id> (also exposed as chat_id_alt), so explicitly configured groups were never authorized — only "*" worked. Match chat_id, chat_id_alt, and the "group:"-stripped form. 2. Reply-to-bot mention bypass (gateway/platforms/signal.py) The new reply-to-bot gate compared the quote author only to the account phone, missing UUID-only authors and timestamp-only quotes. Hoist the quote extraction above the mention filter and reuse _quote_references_own_message (outbound timestamp cache + number<->uuid mapping). Tests: - tests/gateway/test_signal_group_authz.py: explicit-id (via chat_id_alt and group:-strip), wildcard, and deny cases. - tests/gateway/test_signal.py::TestSignalReplyToBotMention: reply-to-bot by timestamp and by UUID author bypass require_mention; reply to a non-bot message still requires a mention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts: # gateway/authz_mixin.py # gateway/session.py
Upstream _adapter_authorization_is_upstream now takes a profile kwarg; widen the test stub lambda so the Signal group-authz tests reach the allowlist branch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…arity-owner-detection # Conflicts: # gateway/run.py # tests/gateway/test_shared_group_sender_prefix.py
What & why
Brings a Signal group to Telegram-parity: any allowed member can talk to the bot, and the bot reliably knows which sender is the owner — without changing upstream's
[name]sender prefix or any other platform's behavior.Supersedes the draft #44706 (which makes only fix (1) below).
The fixes
1. Group-member authorization via
SIGNAL_GROUP_ALLOWED_USERS—gateway/authz_mixin.py_is_user_authorized's group-allowlist bypass only enumerated Telegram and QQBOT, so every Signal group sender fell through to the per-userSIGNAL_ALLOWED_USERScheck and was rejected (Unauthorized user: <uuid> on signal). AddsPlatform.SIGNAL: "SIGNAL_GROUP_ALLOWED_USERS"— the envplatforms/signal.pyalready honors for its own group filter. (This is what #44706 does.)2. Reply-to-bot counts as a mention —
gateway/platforms/signal.pyWith
require_mention: true, quoting a message the bot itself sent now triggers a reply, no@mentionneeded (parity with Telegram/WhatsApp).3.
/slashcommands bypass the mention requirement —gateway/platforms/signal.pyA message starting with
/is handled without an@mention; command-level gating still decides which commands a non-owner may run.4. Owner detection —
gateway/platforms/signal.py,gateway/authz_mixin.py,gateway/session.pyGroup senders arrive as a UUID while the owner allowlist holds a phone; the Signal adapter resolves UUID→phone via its number↔uuid cache and sets
SessionSource.is_ownerat intake. A generic_is_owner()fallback covers the DM path. Wildcard*is never an owner.5. Surfacing the owner — gated, additive, no
[name]change —gateway/run.py,gateway/session.py**Owner:** yes/nocontext line;[name]and prepend an owner-only[SYSTEM: sender NAME is the owner]marker (guests byte-identical to upstream).Both outputs are gated to
Platform.SIGNAL, so Telegram/Slack/Discord/etc. are completely unaffected. The gate is asource.platform in {...}set with a comment inviting other platforms to opt in once their adapter setssource.is_owner.Scope notes
_is_owner, therun.pymarker,session.pyis_owner) is shared with the WhatsApp PR feat(gateway): WhatsApp group authz + owner detection + reactions (fixes #7269) #53623; whichever merges first, the other's shared hunks become no-ops, and the gate set merges to{SIGNAL, WHATSAPP, WHATSAPP_CLOUD}.How to test
.env:SIGNAL_GROUP_ALLOWED_USERS=*,SIGNAL_ALLOWED_USERS=<owner phone>(concrete, not*).config.yaml:signal: { require_mention: true }.@mentions the bot → it replies (previouslyUnauthorized user). (1)@mention→ it replies. (2)/helpfrom a non-owner → handled; an owner-only/command from a non-owner → rejected. (3)[SYSTEM: sender … is the owner]; everyone else plain[name]. (4,5)Tests
New
tests/gateway/test_shared_group_sender_prefix.pycases: owner gets the additive marker (Signal); guest stays plain[name]. All existing gateway prefix/session/signal tests pass underscripts/run_tests.sh(per-file isolation).Platforms tested
macOS (Apple Silicon),
signal-cliREST. Owner-surfacing + parity changes are additive/gated; non-Signal adapters unaffected.Related