From cc03c097c5eb0044266c7c0fef500be0db1ab966 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 7 Mar 2026 23:59:44 +0000 Subject: [PATCH] refactor: share provider group-policy warning collectors --- extensions/discord/src/channel.ts | 54 +++++++-------- extensions/googlechat/src/channel.ts | 32 +++++---- extensions/irc/src/channel.ts | 31 +++++---- extensions/matrix/src/channel.ts | 32 +++++---- extensions/nextcloud-talk/src/channel.ts | 49 +++++++------- extensions/slack/src/channel.ts | 52 +++++++-------- extensions/telegram/src/channel.ts | 47 +++++++------- extensions/whatsapp/src/channel.ts | 47 +++++++------- extensions/zalo/src/channel.ts | 63 +++++++++--------- .../plugins/group-policy-warnings.test.ts | 65 ++++++++++++++++++- src/channels/plugins/group-policy-warnings.ts | 47 ++++++++++++-- src/plugin-sdk/index.ts | 2 + 12 files changed, 300 insertions(+), 221 deletions(-) diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index 4a05b64fb1a..f987e39bab7 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -1,5 +1,6 @@ import { buildAccountScopedDmSecurityPolicy, + collectOpenProviderGroupPolicyWarnings, collectOpenGroupPolicyConfiguredRouteWarnings, createScopedAccountConfigAccessors, formatAllowFromLowercase, @@ -32,8 +33,6 @@ import { resolveDefaultDiscordAccountId, resolveDiscordGroupRequireMention, resolveDiscordGroupToolPolicy, - resolveOpenProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, setAccountEnabledInConfigSection, type ChannelMessageActionAdapter, type ChannelPlugin, @@ -137,38 +136,33 @@ export const discordPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const warnings: string[] = []; - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({ - providerConfigPresent: cfg.channels?.discord !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, - }); const guildEntries = account.config.guilds ?? {}; const guildsConfigured = Object.keys(guildEntries).length > 0; const channelAllowlistConfigured = guildsConfigured; - warnings.push( - ...collectOpenGroupPolicyConfiguredRouteWarnings({ - groupPolicy, - routeAllowlistConfigured: channelAllowlistConfigured, - configureRouteAllowlist: { - surface: "Discord guilds", - openScope: "any channel not explicitly denied", - groupPolicyPath: "channels.discord.groupPolicy", - routeAllowlistPath: "channels.discord.guilds..channels", - }, - missingRouteAllowlist: { - surface: "Discord guilds", - openBehavior: - "with no guild/channel allowlist; any channel can trigger (mention-gated)", - remediation: - 'Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds..channels', - }, - }), - ); - - return warnings; + return collectOpenProviderGroupPolicyWarnings({ + cfg, + providerConfigPresent: cfg.channels?.discord !== undefined, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyConfiguredRouteWarnings({ + groupPolicy, + routeAllowlistConfigured: channelAllowlistConfigured, + configureRouteAllowlist: { + surface: "Discord guilds", + openScope: "any channel not explicitly denied", + groupPolicyPath: "channels.discord.groupPolicy", + routeAllowlistPath: "channels.discord.guilds..channels", + }, + missingRouteAllowlist: { + surface: "Discord guilds", + openBehavior: + "with no guild/channel allowlist; any channel can trigger (mention-gated)", + remediation: + 'Set channels.discord.groupPolicy="allowlist" and configure channels.discord.guilds..channels', + }, + }), + }); }, }, groups: { diff --git a/extensions/googlechat/src/channel.ts b/extensions/googlechat/src/channel.ts index 9b4d5687979..5828efed83b 100644 --- a/extensions/googlechat/src/channel.ts +++ b/extensions/googlechat/src/channel.ts @@ -1,6 +1,7 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyConfigureRouteAllowlistWarning, + collectAllowlistProviderGroupPolicyWarnings, createScopedAccountConfigAccessors, formatNormalizedAllowFromEntries, } from "openclaw/plugin-sdk"; @@ -20,8 +21,6 @@ import { PAIRING_APPROVED_MESSAGE, resolveChannelMediaMaxBytes, resolveGoogleChatGroupRequireMention, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, setAccountEnabledInConfigSection, type ChannelDock, type ChannelMessageActionAdapter, @@ -194,23 +193,22 @@ export const googlechatPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const warnings: string[] = []; - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ + const warnings = collectAllowlistProviderGroupPolicyWarnings({ + cfg, providerConfigPresent: cfg.channels?.googlechat !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + groupPolicy === "open" + ? [ + buildOpenGroupPolicyConfigureRouteAllowlistWarning({ + surface: "Google Chat spaces", + openScope: "any space", + groupPolicyPath: "channels.googlechat.groupPolicy", + routeAllowlistPath: "channels.googlechat.groups", + }), + ] + : [], }); - if (groupPolicy === "open") { - warnings.push( - buildOpenGroupPolicyConfigureRouteAllowlistWarning({ - surface: "Google Chat spaces", - openScope: "any space", - groupPolicyPath: "channels.googlechat.groupPolicy", - routeAllowlistPath: "channels.googlechat.groups", - }), - ); - } if (account.config.dm?.policy === "open") { warnings.push( `- Google Chat DMs are open to anyone. Set channels.googlechat.dm.policy="pairing" or "allowlist".`, diff --git a/extensions/irc/src/channel.ts b/extensions/irc/src/channel.ts index c5b2ed07014..f68ef20edd8 100644 --- a/extensions/irc/src/channel.ts +++ b/extensions/irc/src/channel.ts @@ -1,6 +1,7 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyWarning, + collectAllowlistProviderGroupPolicyWarnings, createScopedAccountConfigAccessors, formatNormalizedAllowFromEntries, } from "openclaw/plugin-sdk"; @@ -12,8 +13,6 @@ import { deleteAccountFromConfigSection, getChatChannelMeta, PAIRING_APPROVED_MESSAGE, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, setAccountEnabledInConfigSection, type ChannelPlugin, } from "openclaw/plugin-sdk/irc"; @@ -142,22 +141,22 @@ export const ircPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const warnings: string[] = []; - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ + const warnings = collectAllowlistProviderGroupPolicyWarnings({ + cfg, providerConfigPresent: cfg.channels?.irc !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + groupPolicy === "open" + ? [ + buildOpenGroupPolicyWarning({ + surface: "IRC channels", + openBehavior: "allows all channels and senders (mention-gated)", + remediation: + 'Prefer channels.irc.groupPolicy="allowlist" with channels.irc.groups', + }), + ] + : [], }); - if (groupPolicy === "open") { - warnings.push( - buildOpenGroupPolicyWarning({ - surface: "IRC channels", - openBehavior: "allows all channels and senders (mention-gated)", - remediation: 'Prefer channels.irc.groupPolicy="allowlist" with channels.irc.groups', - }), - ); - } if (!account.config.tls) { warnings.push( "- IRC TLS is disabled (channels.irc.tls=false); traffic and credentials are plaintext.", diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index a9b2a7c7f77..bdfc097268a 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -1,6 +1,7 @@ import { buildAccountScopedDmSecurityPolicy, buildOpenGroupPolicyWarning, + collectAllowlistProviderGroupPolicyWarnings, createScopedAccountConfigAccessors, } from "openclaw/plugin-sdk"; import { @@ -12,8 +13,6 @@ import { deleteAccountFromConfigSection, normalizeAccountId, PAIRING_APPROVED_MESSAGE, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, setAccountEnabledInConfigSection, type ChannelPlugin, } from "openclaw/plugin-sdk/matrix"; @@ -178,23 +177,22 @@ export const matrixPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg as CoreConfig); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ + return collectAllowlistProviderGroupPolicyWarnings({ + cfg: cfg as CoreConfig, providerConfigPresent: (cfg as CoreConfig).channels?.matrix !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + groupPolicy === "open" + ? [ + buildOpenGroupPolicyWarning({ + surface: "Matrix rooms", + openBehavior: "allows any room to trigger (mention-gated)", + remediation: + 'Set channels.matrix.groupPolicy="allowlist" + channels.matrix.groups (and optionally channels.matrix.groupAllowFrom) to restrict rooms', + }), + ] + : [], }); - if (groupPolicy !== "open") { - return []; - } - return [ - buildOpenGroupPolicyWarning({ - surface: "Matrix rooms", - openBehavior: "allows any room to trigger (mention-gated)", - remediation: - 'Set channels.matrix.groupPolicy="allowlist" + channels.matrix.groups (and optionally channels.matrix.groupAllowFrom) to restrict rooms', - }), - ]; }, }, groups: { diff --git a/extensions/nextcloud-talk/src/channel.ts b/extensions/nextcloud-talk/src/channel.ts index 8f0381cd8d5..56bdb6688da 100644 --- a/extensions/nextcloud-talk/src/channel.ts +++ b/extensions/nextcloud-talk/src/channel.ts @@ -1,5 +1,6 @@ import { buildAccountScopedDmSecurityPolicy, + collectAllowlistProviderGroupPolicyWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, formatAllowFromLowercase, mapAllowFromEntries, @@ -13,8 +14,6 @@ import { DEFAULT_ACCOUNT_ID, deleteAccountFromConfigSection, normalizeAccountId, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, setAccountEnabledInConfigSection, type ChannelPlugin, type OpenClawConfig, @@ -133,31 +132,31 @@ export const nextcloudTalkPlugin: ChannelPlugin = }); }, collectWarnings: ({ account, cfg }) => { - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ - providerConfigPresent: - (cfg.channels as Record | undefined)?.["nextcloud-talk"] !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, - }); const roomAllowlistConfigured = account.config.rooms && Object.keys(account.config.rooms).length > 0; - return collectOpenGroupPolicyRouteAllowlistWarnings({ - groupPolicy, - routeAllowlistConfigured: roomAllowlistConfigured, - restrictSenders: { - surface: "Nextcloud Talk rooms", - openScope: "any member in allowed rooms", - groupPolicyPath: "channels.nextcloud-talk.groupPolicy", - groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom", - }, - noRouteAllowlist: { - surface: "Nextcloud Talk rooms", - routeAllowlistPath: "channels.nextcloud-talk.rooms", - routeScope: "room", - groupPolicyPath: "channels.nextcloud-talk.groupPolicy", - groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom", - }, + return collectAllowlistProviderGroupPolicyWarnings({ + cfg, + providerConfigPresent: + (cfg.channels as Record | undefined)?.["nextcloud-talk"] !== undefined, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyRouteAllowlistWarnings({ + groupPolicy, + routeAllowlistConfigured: roomAllowlistConfigured, + restrictSenders: { + surface: "Nextcloud Talk rooms", + openScope: "any member in allowed rooms", + groupPolicyPath: "channels.nextcloud-talk.groupPolicy", + groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom", + }, + noRouteAllowlist: { + surface: "Nextcloud Talk rooms", + routeAllowlistPath: "channels.nextcloud-talk.rooms", + routeScope: "room", + groupPolicyPath: "channels.nextcloud-talk.groupPolicy", + groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom", + }, + }), }); }, }, diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index ec2faf17744..480b76b3c36 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -1,5 +1,6 @@ import { buildAccountScopedDmSecurityPolicy, + collectOpenProviderGroupPolicyWarnings, collectOpenGroupPolicyConfiguredRouteWarnings, createScopedAccountConfigAccessors, formatAllowFromLowercase, @@ -28,8 +29,6 @@ import { resolveDefaultSlackAccountId, resolveSlackAccount, resolveSlackReplyToMode, - resolveOpenProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, resolveSlackGroupRequireMention, resolveSlackGroupToolPolicy, buildSlackThreadingToolContext, @@ -189,36 +188,31 @@ export const slackPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const warnings: string[] = []; - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({ - providerConfigPresent: cfg.channels?.slack !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, - }); const channelAllowlistConfigured = Boolean(account.config.channels) && Object.keys(account.config.channels ?? {}).length > 0; - warnings.push( - ...collectOpenGroupPolicyConfiguredRouteWarnings({ - groupPolicy, - routeAllowlistConfigured: channelAllowlistConfigured, - configureRouteAllowlist: { - surface: "Slack channels", - openScope: "any channel not explicitly denied", - groupPolicyPath: "channels.slack.groupPolicy", - routeAllowlistPath: "channels.slack.channels", - }, - missingRouteAllowlist: { - surface: "Slack channels", - openBehavior: "with no channel allowlist; any channel can trigger (mention-gated)", - remediation: - 'Set channels.slack.groupPolicy="allowlist" and configure channels.slack.channels', - }, - }), - ); - - return warnings; + return collectOpenProviderGroupPolicyWarnings({ + cfg, + providerConfigPresent: cfg.channels?.slack !== undefined, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyConfiguredRouteWarnings({ + groupPolicy, + routeAllowlistConfigured: channelAllowlistConfigured, + configureRouteAllowlist: { + surface: "Slack channels", + openScope: "any channel not explicitly denied", + groupPolicyPath: "channels.slack.groupPolicy", + routeAllowlistPath: "channels.slack.channels", + }, + missingRouteAllowlist: { + surface: "Slack channels", + openBehavior: "with no channel allowlist; any channel can trigger (mention-gated)", + remediation: + 'Set channels.slack.groupPolicy="allowlist" and configure channels.slack.channels', + }, + }), + }); }, }, groups: { diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index bc492e9754b..a2720821dc8 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -1,4 +1,5 @@ import { + collectAllowlistProviderGroupPolicyWarnings, buildAccountScopedDmSecurityPolicy, collectOpenGroupPolicyRouteAllowlistWarnings, createScopedAccountConfigAccessors, @@ -27,8 +28,6 @@ import { projectCredentialSnapshotFields, resolveConfiguredFromCredentialStatuses, resolveDefaultTelegramAccountId, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, resolveTelegramAccount, resolveTelegramGroupRequireMention, resolveTelegramGroupToolPolicy, @@ -200,30 +199,30 @@ export const telegramPlugin: ChannelPlugin { - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ - providerConfigPresent: cfg.channels?.telegram !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, - }); const groupAllowlistConfigured = account.config.groups && Object.keys(account.config.groups).length > 0; - return collectOpenGroupPolicyRouteAllowlistWarnings({ - groupPolicy, - routeAllowlistConfigured: groupAllowlistConfigured, - restrictSenders: { - surface: "Telegram groups", - openScope: "any member in allowed groups", - groupPolicyPath: "channels.telegram.groupPolicy", - groupAllowFromPath: "channels.telegram.groupAllowFrom", - }, - noRouteAllowlist: { - surface: "Telegram groups", - routeAllowlistPath: "channels.telegram.groups", - routeScope: "group", - groupPolicyPath: "channels.telegram.groupPolicy", - groupAllowFromPath: "channels.telegram.groupAllowFrom", - }, + return collectAllowlistProviderGroupPolicyWarnings({ + cfg, + providerConfigPresent: cfg.channels?.telegram !== undefined, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyRouteAllowlistWarnings({ + groupPolicy, + routeAllowlistConfigured: groupAllowlistConfigured, + restrictSenders: { + surface: "Telegram groups", + openScope: "any member in allowed groups", + groupPolicyPath: "channels.telegram.groupPolicy", + groupAllowFromPath: "channels.telegram.groupAllowFrom", + }, + noRouteAllowlist: { + surface: "Telegram groups", + routeAllowlistPath: "channels.telegram.groups", + routeScope: "group", + groupPolicyPath: "channels.telegram.groupPolicy", + groupAllowFromPath: "channels.telegram.groupAllowFrom", + }, + }), }); }, }, diff --git a/extensions/whatsapp/src/channel.ts b/extensions/whatsapp/src/channel.ts index 0e034fe1df6..8bd2dc950bf 100644 --- a/extensions/whatsapp/src/channel.ts +++ b/extensions/whatsapp/src/channel.ts @@ -1,5 +1,6 @@ import { buildAccountScopedDmSecurityPolicy, + collectAllowlistProviderGroupPolicyWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, } from "openclaw/plugin-sdk"; import { @@ -21,8 +22,6 @@ import { readStringParam, resolveDefaultWhatsAppAccountId, resolveWhatsAppOutboundTarget, - resolveAllowlistProviderRuntimeGroupPolicy, - resolveDefaultGroupPolicy, resolveWhatsAppAccount, resolveWhatsAppConfigAllowFrom, resolveWhatsAppConfigDefaultTo, @@ -136,30 +135,30 @@ export const whatsappPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ - providerConfigPresent: cfg.channels?.whatsapp !== undefined, - groupPolicy: account.groupPolicy, - defaultGroupPolicy, - }); const groupAllowlistConfigured = Boolean(account.groups) && Object.keys(account.groups ?? {}).length > 0; - return collectOpenGroupPolicyRouteAllowlistWarnings({ - groupPolicy, - routeAllowlistConfigured: groupAllowlistConfigured, - restrictSenders: { - surface: "WhatsApp groups", - openScope: "any member in allowed groups", - groupPolicyPath: "channels.whatsapp.groupPolicy", - groupAllowFromPath: "channels.whatsapp.groupAllowFrom", - }, - noRouteAllowlist: { - surface: "WhatsApp groups", - routeAllowlistPath: "channels.whatsapp.groups", - routeScope: "group", - groupPolicyPath: "channels.whatsapp.groupPolicy", - groupAllowFromPath: "channels.whatsapp.groupAllowFrom", - }, + return collectAllowlistProviderGroupPolicyWarnings({ + cfg, + providerConfigPresent: cfg.channels?.whatsapp !== undefined, + configuredGroupPolicy: account.groupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyRouteAllowlistWarnings({ + groupPolicy, + routeAllowlistConfigured: groupAllowlistConfigured, + restrictSenders: { + surface: "WhatsApp groups", + openScope: "any member in allowed groups", + groupPolicyPath: "channels.whatsapp.groupPolicy", + groupAllowFromPath: "channels.whatsapp.groupAllowFrom", + }, + noRouteAllowlist: { + surface: "WhatsApp groups", + routeAllowlistPath: "channels.whatsapp.groups", + routeScope: "group", + groupPolicyPath: "channels.whatsapp.groupPolicy", + groupAllowFromPath: "channels.whatsapp.groupAllowFrom", + }, + }), }); }, }, diff --git a/extensions/zalo/src/channel.ts b/extensions/zalo/src/channel.ts index 454a29b3cbd..8b32041e7f1 100644 --- a/extensions/zalo/src/channel.ts +++ b/extensions/zalo/src/channel.ts @@ -1,5 +1,6 @@ import { buildAccountScopedDmSecurityPolicy, + collectOpenProviderGroupPolicyWarnings, buildOpenGroupPolicyRestrictSendersWarning, buildOpenGroupPolicyWarning, mapAllowFromEntries, @@ -27,8 +28,6 @@ import { isNumericTargetId, PAIRING_APPROVED_MESSAGE, resolveOutboundMediaUrls, - resolveDefaultGroupPolicy, - resolveOpenProviderRuntimeGroupPolicy, sendPayloadWithChunkedTextAndMedia, setAccountEnabledInConfigSection, } from "openclaw/plugin-sdk/zalo"; @@ -150,37 +149,39 @@ export const zaloPlugin: ChannelPlugin = { }); }, collectWarnings: ({ account, cfg }) => { - const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg); - const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({ + return collectOpenProviderGroupPolicyWarnings({ + cfg, providerConfigPresent: cfg.channels?.zalo !== undefined, - groupPolicy: account.config.groupPolicy, - defaultGroupPolicy, + configuredGroupPolicy: account.config.groupPolicy, + collect: (groupPolicy) => { + if (groupPolicy !== "open") { + return []; + } + const explicitGroupAllowFrom = mapAllowFromEntries(account.config.groupAllowFrom); + const dmAllowFrom = mapAllowFromEntries(account.config.allowFrom); + const effectiveAllowFrom = + explicitGroupAllowFrom.length > 0 ? explicitGroupAllowFrom : dmAllowFrom; + if (effectiveAllowFrom.length > 0) { + return [ + buildOpenGroupPolicyRestrictSendersWarning({ + surface: "Zalo groups", + openScope: "any member", + groupPolicyPath: "channels.zalo.groupPolicy", + groupAllowFromPath: "channels.zalo.groupAllowFrom", + }), + ]; + } + return [ + buildOpenGroupPolicyWarning({ + surface: "Zalo groups", + openBehavior: + "with no groupAllowFrom/allowFrom allowlist; any member can trigger (mention-gated)", + remediation: + 'Set channels.zalo.groupPolicy="allowlist" + channels.zalo.groupAllowFrom', + }), + ]; + }, }); - if (groupPolicy !== "open") { - return []; - } - const explicitGroupAllowFrom = mapAllowFromEntries(account.config.groupAllowFrom); - const dmAllowFrom = mapAllowFromEntries(account.config.allowFrom); - const effectiveAllowFrom = - explicitGroupAllowFrom.length > 0 ? explicitGroupAllowFrom : dmAllowFrom; - if (effectiveAllowFrom.length > 0) { - return [ - buildOpenGroupPolicyRestrictSendersWarning({ - surface: "Zalo groups", - openScope: "any member", - groupPolicyPath: "channels.zalo.groupPolicy", - groupAllowFromPath: "channels.zalo.groupAllowFrom", - }), - ]; - } - return [ - buildOpenGroupPolicyWarning({ - surface: "Zalo groups", - openBehavior: - "with no groupAllowFrom/allowFrom allowlist; any member can trigger (mention-gated)", - remediation: 'Set channels.zalo.groupPolicy="allowlist" + channels.zalo.groupAllowFrom', - }), - ]; }, }, groups: { diff --git a/src/channels/plugins/group-policy-warnings.test.ts b/src/channels/plugins/group-policy-warnings.test.ts index e25493a74a7..51a77d992f1 100644 --- a/src/channels/plugins/group-policy-warnings.test.ts +++ b/src/channels/plugins/group-policy-warnings.test.ts @@ -1,7 +1,9 @@ import { describe, expect, it } from "vitest"; import { - collectOpenGroupPolicyConfiguredRouteWarnings, + collectAllowlistProviderGroupPolicyWarnings, collectAllowlistProviderRestrictSendersWarnings, + collectOpenGroupPolicyConfiguredRouteWarnings, + collectOpenProviderGroupPolicyWarnings, collectOpenGroupPolicyRestrictSendersWarnings, collectOpenGroupPolicyRouteAllowlistWarnings, buildOpenGroupPolicyConfigureRouteAllowlistWarning, @@ -126,6 +128,67 @@ describe("group policy warning builders", () => { ]); }); + it("passes resolved allowlist-provider policy into the warning collector", () => { + expect( + collectAllowlistProviderGroupPolicyWarnings({ + cfg: { + channels: { + defaults: { groupPolicy: "open" }, + }, + }, + providerConfigPresent: false, + configuredGroupPolicy: undefined, + collect: (groupPolicy) => [groupPolicy], + }), + ).toEqual(["allowlist"]); + + expect( + collectAllowlistProviderGroupPolicyWarnings({ + cfg: { + channels: { + defaults: { groupPolicy: "disabled" }, + }, + }, + providerConfigPresent: true, + configuredGroupPolicy: "open", + collect: (groupPolicy) => [groupPolicy], + }), + ).toEqual(["open"]); + }); + + it("passes resolved open-provider policy into the warning collector", () => { + expect( + collectOpenProviderGroupPolicyWarnings({ + cfg: { + channels: { + defaults: { groupPolicy: "allowlist" }, + }, + }, + providerConfigPresent: false, + configuredGroupPolicy: undefined, + collect: (groupPolicy) => [groupPolicy], + }), + ).toEqual(["allowlist"]); + + expect( + collectOpenProviderGroupPolicyWarnings({ + cfg: {}, + providerConfigPresent: true, + configuredGroupPolicy: undefined, + collect: (groupPolicy) => [groupPolicy], + }), + ).toEqual(["open"]); + + expect( + collectOpenProviderGroupPolicyWarnings({ + cfg: {}, + providerConfigPresent: true, + configuredGroupPolicy: "disabled", + collect: (groupPolicy) => [groupPolicy], + }), + ).toEqual(["disabled"]); + }); + it("collects route allowlist warning variants", () => { const params = { groupPolicy: "open" as const, diff --git a/src/channels/plugins/group-policy-warnings.ts b/src/channels/plugins/group-policy-warnings.ts index e232c40abf1..67d8c952b02 100644 --- a/src/channels/plugins/group-policy-warnings.ts +++ b/src/channels/plugins/group-policy-warnings.ts @@ -2,9 +2,12 @@ import type { OpenClawConfig } from "../../config/config.js"; import { resolveAllowlistProviderRuntimeGroupPolicy, resolveDefaultGroupPolicy, + resolveOpenProviderRuntimeGroupPolicy, } from "../../config/runtime-group-policy.js"; import type { GroupPolicy } from "../../config/types.base.js"; +type GroupPolicyWarningCollector = (groupPolicy: GroupPolicy) => string[]; + export function buildOpenGroupPolicyWarning(params: { surface: string; openBehavior: string; @@ -77,20 +80,50 @@ export function collectAllowlistProviderRestrictSendersWarnings( configuredGroupPolicy?: GroupPolicy | null; } & Omit[0], "groupPolicy">, ): string[] { + return collectAllowlistProviderGroupPolicyWarnings({ + cfg: params.cfg, + providerConfigPresent: params.providerConfigPresent, + configuredGroupPolicy: params.configuredGroupPolicy, + collect: (groupPolicy) => + collectOpenGroupPolicyRestrictSendersWarnings({ + groupPolicy, + surface: params.surface, + openScope: params.openScope, + groupPolicyPath: params.groupPolicyPath, + groupAllowFromPath: params.groupAllowFromPath, + mentionGated: params.mentionGated, + }), + }); +} + +export function collectAllowlistProviderGroupPolicyWarnings(params: { + cfg: OpenClawConfig; + providerConfigPresent: boolean; + configuredGroupPolicy?: GroupPolicy | null; + collect: GroupPolicyWarningCollector; +}): string[] { const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg); const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({ providerConfigPresent: params.providerConfigPresent, groupPolicy: params.configuredGroupPolicy ?? undefined, defaultGroupPolicy, }); - return collectOpenGroupPolicyRestrictSendersWarnings({ - groupPolicy, - surface: params.surface, - openScope: params.openScope, - groupPolicyPath: params.groupPolicyPath, - groupAllowFromPath: params.groupAllowFromPath, - mentionGated: params.mentionGated, + return params.collect(groupPolicy); +} + +export function collectOpenProviderGroupPolicyWarnings(params: { + cfg: OpenClawConfig; + providerConfigPresent: boolean; + configuredGroupPolicy?: GroupPolicy | null; + collect: GroupPolicyWarningCollector; +}): string[] { + const defaultGroupPolicy = resolveDefaultGroupPolicy(params.cfg); + const { groupPolicy } = resolveOpenProviderRuntimeGroupPolicy({ + providerConfigPresent: params.providerConfigPresent, + groupPolicy: params.configuredGroupPolicy ?? undefined, + defaultGroupPolicy, }); + return params.collect(groupPolicy); } export function collectOpenGroupPolicyRouteAllowlistWarnings(params: { diff --git a/src/plugin-sdk/index.ts b/src/plugin-sdk/index.ts index 1381d6eda5c..ca3f54a479b 100644 --- a/src/plugin-sdk/index.ts +++ b/src/plugin-sdk/index.ts @@ -547,7 +547,9 @@ export { buildOpenGroupPolicyNoRouteAllowlistWarning, buildOpenGroupPolicyRestrictSendersWarning, buildOpenGroupPolicyWarning, + collectAllowlistProviderGroupPolicyWarnings, collectAllowlistProviderRestrictSendersWarnings, + collectOpenProviderGroupPolicyWarnings, collectOpenGroupPolicyConfiguredRouteWarnings, collectOpenGroupPolicyRestrictSendersWarnings, collectOpenGroupPolicyRouteAllowlistWarnings,