feat(whatsapp): per-group allowFrom for sender authorization#81053
feat(whatsapp): per-group allowFrom for sender authorization#81053jack-stormentswe wants to merge 1 commit into
Conversation
…w#69926) Bring WhatsApp to parity with Feishu, IRC, LINE, Telegram, and Nextcloud-talk by accepting a per-group `allowFrom` list on `channels.whatsapp.groups.<jid>` (and the matching account-scoped entry). A non-empty per-group `allowFrom` overrides (does not merge with) the channel/account-level `groupAllowFrom` for that one JID, while groups without an override keep the channel/account-level list. Empty arrays fall back to the channel/account-level allowlist. The resolver respects the same multi-account inheritance guard as the rest of the WhatsApp config: account-scoped `groups[<jid>].allowFrom` takes precedence over a root-scope entry for the same JID, so root groups do not silently leak into accounts with their own groups map. Changes: - `WhatsAppGroupEntrySchema` and `WhatsAppGroupConfig` gain an optional `allowFrom: string[]` (E.164 phone numbers). - New `resolveWhatsAppConversationGroupAllowFrom` resolver in `inbound-policy.ts`, wired into `resolveWhatsAppIngressAccess` so the effective `groupAllowFrom` passed to `resolveStableChannelMessageIngress` reflects the per-group override for group conversations. - `bundled-channel-config-metadata.generated.ts` regenerated to surface the new field in the bundled channel schema. - `docs/channels/whatsapp.md` documents the per-group override and shows a mixed-trust example. Tests: - New `extensions/whatsapp/src/inbound-policy.test.ts` covers the resolver semantics: override applied, empty list falls back, unrelated groups inherit the account-level list, account-scoped groups override root-scope entries, missing groups map returns the account-level allowlist. - `extensions/whatsapp/src/config-schema.test.ts` gains two cases for the new field at root and account scope. Fixes openclaw#69926.
|
Codex review: needs maintainer review before merge. Summary Reproducibility: yes. for the feature gap: current main's strict WhatsApp group schema omits Real behavior proof Next step before merge Security Review detailsBest possible solution: Land a WhatsApp-owned additive config/runtime/docs update that preserves existing channel-level fallback while allowing explicit per-group sender authorization. Do we have a high-confidence way to reproduce the issue? Yes for the feature gap: current main's strict WhatsApp group schema omits Is this the best way to solve the issue? Yes. The PR keeps the change inside the WhatsApp plugin/config surface, reuses the existing stable ingress resolver, and avoids changing unrelated group admission or global denylist behavior. What I checked:
Likely related people:
Remaining risk / open question:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 7c2c91a0fa8b. |
Fixes #69926.
Brings WhatsApp to parity with Feishu, IRC, LINE, Telegram, and Nextcloud-talk by accepting a per-group
allowFromlist onchannels.whatsapp.groups.<jid>(and the matching account-scoped entry). A non-empty per-groupallowFromoverrides (does not merge with) the channel/account-levelgroupAllowFromfor that one JID, while groups without an override keep the channel/account-level list. Empty arrays fall back to the channel/account-level allowlist.ClawSweeper triage explicitly cleared this as "narrow, source-proven feature gap with clear files, tests, and boundaries."
Change
WhatsAppGroupEntrySchemaandWhatsAppGroupConfiggain an optionalallowFrom: string[](E.164 phone numbers).resolveWhatsAppConversationGroupAllowFromresolver inextensions/whatsapp/src/inbound-policy.ts, wired intoresolveWhatsAppIngressAccessso the effectivegroupAllowFrompassed toresolveStableChannelMessageIngressreflects the per-group override for group conversations.groups[<jid>].allowFromtakes precedence over a root-scope entry for the same JID, so root groups do not silently leak into accounts with their own groups map.bundled-channel-config-metadata.generated.tsregenerated to surface the new field in the bundled channel schema (verified viapnpm config:channels:check).docs/channels/whatsapp.mddocuments the per-group override semantics and shows a mixed-trust example.Verification
pnpm test extensions/whatsapp/src/inbound-policy.test.ts extensions/whatsapp/src/config-schema.test.ts-- 17/17 pass (6 new resolver cases + 2 new schema cases + existing schema tests).pnpm config:channels:checkclean.pnpm exec oxfmt --check --threads=1clean on every touched file.Real behavior proof
Behavior addressed: operators should be able to express different sender allowlists per WhatsApp group (e.g. "Group A: any team member can drive the bot; Group B: only staff; Group C: only me") without dropping back to a single channel-wide allowlist. The runtime resolver should also honor the multi-account inheritance guard so root-scope per-group lists do not leak into accounts with their own groups map.
Real environment tested: local Linux 6.17 (Node 22.22.2). Live Node process driving the real production
resolveWhatsAppInboundPolicyandresolveWhatsAppConversationGroupAllowFromhelpers from this PR (no mocks). The proof feeds the helper realistic OpenClaw configs (single-account, multi-account, override+inherit+empty cases) and prints the effectivegroupAllowFromthe inbound gate would apply.Exact steps or command run after this patch:
Where
/tmp/proof-69926.mtsdirectly imports the production resolver helpers fromextensions/whatsapp/src/inbound-policy.tsand validates per-group override, fallback on empty, account-scoped override, and inheritance behavior across seven scenarios.Evidence after fix: terminal output captured directly from the live Node process exercising the production helpers.
Observed result after fix:
allowFromreturns exactly that list, suppressing the channel-levelgroupAllowFromfor that JID.groupAllowFrom, so existing configs keep working unchanged.allowFrom: []falls back to the channel-level list, so accidental empties do not silently fail closed for the group.groupAllowFromrather than leaking from the personal account or the channel root.groupsmap of its own, falls back to its own account-levelgroupAllowFrom.What was not tested: a live WhatsApp message ingress against a real WhatsApp Web account. The runtime proof above exercises the production resolver and
resolveWhatsAppInboundPolicychain, and the unit suite covers the schema acceptance plus the resolver semantics under every documented case, but neither runs a real inbound WhatsApp event against a paired account.