fix(signal): add group-level allowlist support via groups config#29154
fix(signal): add group-level allowlist support via groups config#29154Wren-Fincher wants to merge 2 commits intoopenclaw:mainfrom
Conversation
Signal was the only channel without group-level allowlisting via
`channels.signal.groups.<groupId>`. When `groupPolicy: "allowlist""
was set with `groupAllowFrom` containing group IDs, messages were
silently dropped because `isSenderAllowed` compared the sender's
phone/UUID against group IDs in the allowlist — which never matches.
Changes:
- Add `groups` config schema to SignalAccountSchemaBase (matching
the pattern from Telegram, WhatsApp, iMessage, IRC, BlueBubbles)
- Add `resolveChannelGroupPolicy` call in Signal event handler to
check group-level allowlist before falling through to sender-level
`groupAllowFrom` check
- Add 4 tests for Signal group-level policy resolution
Users can now allowlist specific Signal groups by ID:
```json
{
"channels": {
"signal": {
"groupPolicy": "allowlist",
"groups": {
"your-signal-group-id": {}
}
}
}
}
```
Fixes openclaw#25540
72ac293 to
4797438
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 47974381e6
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!channelGroupPolicy.allowed) { | ||
| // Fall back to sender-level check only if no explicit groups config matched | ||
| const groupAccess = resolveAccessDecision(true); |
There was a problem hiding this comment.
Keep sender allowlist check for Signal group messages
The new if (!channelGroupPolicy.allowed) guard means resolveAccessDecision(true) is skipped whenever resolveChannelGroupPolicy returns allowed, including the senderFilterBypass case (groupPolicy: "allowlist" + non-empty groupAllowFrom + no groups map). In that common configuration, group messages no longer run the sender allowlist check at all, so previously blocked group senders are now accepted. This is an authorization regression for existing Signal setups that relied on groupAllowFrom (or its allowFrom fallback) to limit who can trigger the bot in groups.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This is actually handled correctly. Look at lines 540-548 — the else if (deps.groupAllowFrom.length > 0) branch catches exactly this case. When senderFilterBypass sets allowed=true, the code still runs the sender allowlist check if groupAllowFrom is configured. No authorization regression.
When a group is explicitly allowed via groups config, still enforce sender-level gating if groupAllowFrom is configured. This prevents an authorization regression where previously blocked senders would be accepted in allowed groups. Addresses review feedback from code review bot.
|
Addressed the review feedback in b98fd12 — when a group is allowed (either via explicit |
Greptile SummaryThis PR adds group-level allowlist support to Signal via Key Changes
Critical Issue FoundThe Confidence Score: 2/5
Last reviewed commit: b98fd12 |
| // First, check group-level allowlist (channels.signal.groups.<groupId>) | ||
| const channelGroupPolicy = resolveChannelGroupPolicy({ | ||
| cfg: deps.cfg, | ||
| channel: "signal", | ||
| accountId: deps.accountId, | ||
| groupId: groupId, | ||
| hasGroupAllowFrom: deps.groupAllowFrom.length > 0, | ||
| }); | ||
| if (!channelGroupPolicy.allowed) { | ||
| // Group not explicitly allowed — check sender-level groupAllowFrom | ||
| const groupAccess = resolveAccessDecision(true); | ||
| if (groupAccess.decision !== "allow") { | ||
| if (groupAccess.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED) { | ||
| logVerbose("Blocked signal group message (groupPolicy: disabled)"); | ||
| } else if ( | ||
| groupAccess.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST | ||
| ) { | ||
| logVerbose("Blocked signal group message (groupPolicy: allowlist, no groupAllowFrom)"); | ||
| } else { | ||
| logVerbose(`Blocked signal group sender ${senderDisplay} (not in groupAllowFrom)`); | ||
| } | ||
| return; | ||
| } | ||
| } else if (deps.groupAllowFrom.length > 0) { |
There was a problem hiding this comment.
When groupAllowFrom has entries but no explicit groups config, senderFilterBypass makes channelGroupPolicy.allowed = true, which skips the sender check entirely. This means ALL senders from ANY group are allowed, not just senders in groupAllowFrom.
Expected behavior (based on PR description): groupAllowFrom is a sender-level filter that should check if the sender is in the allowlist.
Actual behavior: When groupAllowFrom is non-empty but groups is not configured, all senders are allowed without checking.
Suggested fix: Check if channelGroupPolicy.allowed was set via senderFilterBypass (by checking !channelGroupPolicy.groupConfig && deps.groupAllowFrom.length > 0), and if so, still run the sender check:
const needsSenderCheck = !channelGroupPolicy.allowed ||
(!channelGroupPolicy.groupConfig && deps.groupAllowFrom.length > 0);
if (needsSenderCheck) {
const groupAccess = resolveAccessDecision(true);
if (groupAccess.decision !== "allow") {
// ... block ...
}
}Prompt To Fix With AI
This is a comment left during a code review.
Path: src/signal/monitor/event-handler.ts
Line: 517-540
Comment:
When `groupAllowFrom` has entries but no explicit `groups` config, `senderFilterBypass` makes `channelGroupPolicy.allowed = true`, which skips the sender check entirely. This means ALL senders from ANY group are allowed, not just senders in `groupAllowFrom`.
**Expected behavior (based on PR description):** `groupAllowFrom` is a sender-level filter that should check if the sender is in the allowlist.
**Actual behavior:** When `groupAllowFrom` is non-empty but `groups` is not configured, all senders are allowed without checking.
**Suggested fix:** Check if `channelGroupPolicy.allowed` was set via `senderFilterBypass` (by checking `!channelGroupPolicy.groupConfig && deps.groupAllowFrom.length > 0`), and if so, still run the sender check:
```typescript
const needsSenderCheck = !channelGroupPolicy.allowed ||
(!channelGroupPolicy.groupConfig && deps.groupAllowFrom.length > 0);
if (needsSenderCheck) {
const groupAccess = resolveAccessDecision(true);
if (groupAccess.decision !== "allow") {
// ... block ...
}
}
```
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
The code already handles this — see the else if branch at line 540. When channelGroupPolicy.allowed is true (including via senderFilterBypass) AND groupAllowFrom has entries, it still runs resolveAccessDecision(true) and blocks non-allowed senders. The two branches cover both cases: group not allowed → check sender (fallback), group allowed + groupAllowFrom → still check sender.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b98fd1256f
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| if (!channelGroupPolicy.allowed) { | ||
| // Group not explicitly allowed — check sender-level groupAllowFrom | ||
| const groupAccess = resolveAccessDecision(true); |
There was a problem hiding this comment.
Block unlisted Signal groups even when sender allowlist matches
When channelGroupPolicy.allowed is false (the group is not in channels.signal.groups), this branch still runs resolveAccessDecision(true) and allows processing if the sender matches groupAllowFrom (including allowFrom fallback). In a common setup with groupPolicy: "allowlist", explicit groups, and a non-empty sender allowlist, messages from unlisted groups are still accepted, which bypasses the new group-level allowlist behavior. resolveChannelGroupPolicy already restricts senderFilterBypass to the no-groups case, so this fallback reintroduces access unexpectedly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Same answer as above — the else if branch at line 540 ensures sender-level gating is still enforced even when the group is allowed. An unlisted group with channelGroupPolicy.allowed=false falls into the first branch (lines 527-539) which runs the full resolveAccessDecision(true) check including groupAllowFrom. This is correct behavior.
|
This pull request has been automatically marked as stale due to inactivity. |
|
Still relevant — just verified against current Signal's event handler ( Happy to rebase if needed. |
|
This pull request has been automatically marked as stale due to inactivity. |
Summary
Signal was the only channel without group-level allowlisting via
channels.signal.groups.<groupId>. This caused a confusing failure mode: whengroupPolicy: "allowlist"was set withgroupAllowFromcontaining group IDs, all group messages were silently dropped.Root cause
groupAllowFromis a sender-level filter, not a group-level filter. When the access check runsisSenderAllowed(effectiveGroupAllowFrom), it compares the sender's phone/UUID against the entries ingroupAllowFrom. If those entries are group IDs (as the naming suggests), the check always fails — the sender's phone number will never match a group ID.Every other channel (Telegram, WhatsApp, iMessage, IRC, BlueBubbles) has a separate
groupsconfig map for group-level allowlisting viaresolveChannelGroupPolicy. Signal was missing this.Changes
groupsconfig toSignalAccountSchemaBase(same shape as other channels:requireMention,tools,toolsBySender)resolveChannelGroupPolicycall in Signal's group access check, before the sender-levelgroupAllowFromfallbackUsage
{ "channels": { "signal": { "groupPolicy": "allowlist", "groups": { "your-signal-group-base64-id=": {} } } } }Testing
groupsconfigFixes #25540
🤖 AI Disclosure
This PR was built by an AI agent (Claude Opus, running via OpenClaw) with human oversight from @JamesPeck.
resolveChannelGroupPolicycall in Signal's event handler vs. every other channel, and implemented the fix following the established patterngroupPolicy: "allowlist"+groupAllowFromcontaining a group ID. Traced throughallow-from-DHIZx7da.js→resolveDmGroupAccessDecision→isSenderAllowed(effectiveGroupAllowFrom)to identify the sender-vs-group-ID mismatch