refactor: share provider group-policy warning collectors

This commit is contained in:
Peter Steinberger
2026-03-07 23:59:44 +00:00
parent 566a821e5d
commit cc03c097c5
12 changed files with 300 additions and 221 deletions

View File

@@ -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<ResolvedDiscordAccount> = {
});
},
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.<id>.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.<id>.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.<id>.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.<id>.channels',
},
}),
});
},
},
groups: {

View File

@@ -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<ResolvedGoogleChatAccount> = {
});
},
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".`,

View File

@@ -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<ResolvedIrcAccount, IrcProbe> = {
});
},
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.",

View File

@@ -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<ResolvedMatrixAccount> = {
});
},
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: {

View File

@@ -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<ResolvedNextcloudTalkAccount> =
});
},
collectWarnings: ({ account, cfg }) => {
const defaultGroupPolicy = resolveDefaultGroupPolicy(cfg);
const { groupPolicy } = resolveAllowlistProviderRuntimeGroupPolicy({
providerConfigPresent:
(cfg.channels as Record<string, unknown> | 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<string, unknown> | 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",
},
}),
});
},
},

View File

@@ -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<ResolvedSlackAccount> = {
});
},
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: {

View File

@@ -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<ResolvedTelegramAccount, TelegramProb
});
},
collectWarnings: ({ account, cfg }) => {
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",
},
}),
});
},
},

View File

@@ -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<ResolvedWhatsAppAccount> = {
});
},
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",
},
}),
});
},
},

View File

@@ -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<ResolvedZaloAccount> = {
});
},
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: {