refactor: centralize open group-policy warning flow collectors

This commit is contained in:
Peter Steinberger
2026-03-07 22:45:40 +00:00
parent b456649974
commit b7d03ea1f5
15 changed files with 272 additions and 190 deletions

View File

@@ -1,6 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRestrictSendersWarnings,
} from "openclaw/plugin-sdk";
import type {
ChannelAccountSnapshot,
@@ -140,18 +140,14 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount> = {
},
collectWarnings: ({ account }) => {
const groupPolicy = account.config.groupPolicy ?? "allowlist";
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "BlueBubbles groups",
openScope: "any member",
groupPolicyPath: "channels.bluebubbles.groupPolicy",
groupAllowFromPath: "channels.bluebubbles.groupAllowFrom",
mentionGated: false,
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "BlueBubbles groups",
openScope: "any member",
groupPolicyPath: "channels.bluebubbles.groupPolicy",
groupAllowFromPath: "channels.bluebubbles.groupAllowFrom",
mentionGated: false,
});
},
},
messaging: {

View File

@@ -1,7 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyConfigureRouteAllowlistWarning,
buildOpenGroupPolicyWarning,
collectOpenGroupPolicyConfiguredRouteWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -150,28 +149,25 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
const guildsConfigured = Object.keys(guildEntries).length > 0;
const channelAllowlistConfigured = guildsConfigured;
if (groupPolicy === "open") {
if (channelAllowlistConfigured) {
warnings.push(
buildOpenGroupPolicyConfigureRouteAllowlistWarning({
surface: "Discord guilds",
openScope: "any channel not explicitly denied",
groupPolicyPath: "channels.discord.groupPolicy",
routeAllowlistPath: "channels.discord.guilds.<id>.channels",
}),
);
} else {
warnings.push(
buildOpenGroupPolicyWarning({
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',
}),
);
}
}
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;
},

View File

@@ -1,4 +1,4 @@
import { buildOpenGroupPolicyRestrictSendersWarning } from "openclaw/plugin-sdk";
import { collectOpenGroupPolicyRestrictSendersWarnings } from "openclaw/plugin-sdk";
import type { ChannelMeta, ChannelPlugin, ClawdbotConfig } from "openclaw/plugin-sdk/feishu";
import {
buildProbeChannelStatusSummary,
@@ -267,15 +267,13 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
groupPolicy: feishuCfg?.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") return [];
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: `Feishu[${account.accountId}] groups`,
openScope: "any member",
groupPolicyPath: "channels.feishu.groupPolicy",
groupAllowFromPath: "channels.feishu.groupAllowFrom",
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: `Feishu[${account.accountId}] groups`,
openScope: "any member",
groupPolicyPath: "channels.feishu.groupPolicy",
groupAllowFromPath: "channels.feishu.groupAllowFrom",
});
},
},
setup: {

View File

@@ -1,6 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRestrictSendersWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -151,18 +151,14 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "iMessage groups",
openScope: "any member",
groupPolicyPath: "channels.imessage.groupPolicy",
groupAllowFromPath: "channels.imessage.groupAllowFrom",
mentionGated: false,
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "iMessage groups",
openScope: "any member",
groupPolicyPath: "channels.imessage.groupPolicy",
groupAllowFromPath: "channels.imessage.groupAllowFrom",
mentionGated: false,
});
},
},
groups: {

View File

@@ -1,6 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRestrictSendersWarnings,
} from "openclaw/plugin-sdk";
import {
buildChannelConfigSchema,
@@ -181,18 +181,14 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "LINE groups",
openScope: "any member in groups",
groupPolicyPath: "channels.line.groupPolicy",
groupAllowFromPath: "channels.line.groupAllowFrom",
mentionGated: false,
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "LINE groups",
openScope: "any member in groups",
groupPolicyPath: "channels.line.groupPolicy",
groupAllowFromPath: "channels.line.groupAllowFrom",
mentionGated: false,
});
},
},
groups: {

View File

@@ -1,6 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRestrictSendersWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -301,17 +301,13 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "Mattermost channels",
openScope: "any member",
groupPolicyPath: "channels.mattermost.groupPolicy",
groupAllowFromPath: "channels.mattermost.groupAllowFrom",
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "Mattermost channels",
openScope: "any member",
groupPolicyPath: "channels.mattermost.groupPolicy",
groupAllowFromPath: "channels.mattermost.groupAllowFrom",
});
},
},
groups: {

View File

@@ -1,4 +1,4 @@
import { buildOpenGroupPolicyRestrictSendersWarning } from "openclaw/plugin-sdk";
import { collectOpenGroupPolicyRestrictSendersWarnings } from "openclaw/plugin-sdk";
import type {
ChannelMessageActionName,
ChannelPlugin,
@@ -141,17 +141,13 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
groupPolicy: cfg.channels?.msteams?.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "MS Teams groups",
openScope: "any member",
groupPolicyPath: "channels.msteams.groupPolicy",
groupAllowFromPath: "channels.msteams.groupAllowFrom",
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "MS Teams groups",
openScope: "any member",
groupPolicyPath: "channels.msteams.groupPolicy",
groupAllowFromPath: "channels.msteams.groupAllowFrom",
});
},
},
setup: {

View File

@@ -1,7 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyNoRouteAllowlistWarning,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRouteAllowlistWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -140,30 +139,25 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
const roomAllowlistConfigured =
account.config.rooms && Object.keys(account.config.rooms).length > 0;
if (roomAllowlistConfigured) {
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "Nextcloud Talk rooms",
openScope: "any member in allowed rooms",
groupPolicyPath: "channels.nextcloud-talk.groupPolicy",
groupAllowFromPath: "channels.nextcloud-talk.groupAllowFrom",
}),
];
}
return [
buildOpenGroupPolicyNoRouteAllowlistWarning({
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",
}),
];
},
});
},
},
groups: {

View File

@@ -1,6 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRestrictSendersWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -175,18 +175,14 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "Signal groups",
openScope: "any member",
groupPolicyPath: "channels.signal.groupPolicy",
groupAllowFromPath: "channels.signal.groupAllowFrom",
mentionGated: false,
}),
];
return collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy,
surface: "Signal groups",
openScope: "any member",
groupPolicyPath: "channels.signal.groupPolicy",
groupAllowFromPath: "channels.signal.groupAllowFrom",
mentionGated: false,
});
},
},
messaging: {

View File

@@ -1,7 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyConfigureRouteAllowlistWarning,
buildOpenGroupPolicyWarning,
collectOpenGroupPolicyConfiguredRouteWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -199,27 +198,24 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
const channelAllowlistConfigured =
Boolean(account.config.channels) && Object.keys(account.config.channels ?? {}).length > 0;
if (groupPolicy === "open") {
if (channelAllowlistConfigured) {
warnings.push(
buildOpenGroupPolicyConfigureRouteAllowlistWarning({
surface: "Slack channels",
openScope: "any channel not explicitly denied",
groupPolicyPath: "channels.slack.groupPolicy",
routeAllowlistPath: "channels.slack.channels",
}),
);
} else {
warnings.push(
buildOpenGroupPolicyWarning({
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',
}),
);
}
}
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;
},

View File

@@ -1,7 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyNoRouteAllowlistWarning,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRouteAllowlistWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -210,30 +209,25 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
groupPolicy: account.config.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
const groupAllowlistConfigured =
account.config.groups && Object.keys(account.config.groups).length > 0;
if (groupAllowlistConfigured) {
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "Telegram groups",
openScope: "any member in allowed groups",
groupPolicyPath: "channels.telegram.groupPolicy",
groupAllowFromPath: "channels.telegram.groupAllowFrom",
}),
];
}
return [
buildOpenGroupPolicyNoRouteAllowlistWarning({
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",
}),
];
},
});
},
},
groups: {

View File

@@ -1,7 +1,6 @@
import {
buildAccountScopedDmSecurityPolicy,
buildOpenGroupPolicyNoRouteAllowlistWarning,
buildOpenGroupPolicyRestrictSendersWarning,
collectOpenGroupPolicyRouteAllowlistWarnings,
} from "openclaw/plugin-sdk";
import {
applyAccountNameToChannelSection,
@@ -143,30 +142,25 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
groupPolicy: account.groupPolicy,
defaultGroupPolicy,
});
if (groupPolicy !== "open") {
return [];
}
const groupAllowlistConfigured =
Boolean(account.groups) && Object.keys(account.groups ?? {}).length > 0;
if (groupAllowlistConfigured) {
return [
buildOpenGroupPolicyRestrictSendersWarning({
surface: "WhatsApp groups",
openScope: "any member in allowed groups",
groupPolicyPath: "channels.whatsapp.groupPolicy",
groupAllowFromPath: "channels.whatsapp.groupAllowFrom",
}),
];
}
return [
buildOpenGroupPolicyNoRouteAllowlistWarning({
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",
}),
];
},
});
},
},
setup: {

View File

@@ -1,5 +1,8 @@
import { describe, expect, it } from "vitest";
import {
collectOpenGroupPolicyConfiguredRouteWarnings,
collectOpenGroupPolicyRestrictSendersWarnings,
collectOpenGroupPolicyRouteAllowlistWarnings,
buildOpenGroupPolicyConfigureRouteAllowlistWarning,
buildOpenGroupPolicyNoRouteAllowlistWarning,
buildOpenGroupPolicyRestrictSendersWarning,
@@ -58,4 +61,91 @@ describe("group policy warning builders", () => {
'- Example channels: groupPolicy="open" allows any channel not explicitly denied to trigger (mention-gated). Set channels.example.groupPolicy="allowlist" and configure channels.example.channels.',
);
});
it("collects restrict-senders warning only for open policy", () => {
expect(
collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy: "allowlist",
surface: "Example groups",
openScope: "any member",
groupPolicyPath: "channels.example.groupPolicy",
groupAllowFromPath: "channels.example.groupAllowFrom",
}),
).toEqual([]);
expect(
collectOpenGroupPolicyRestrictSendersWarnings({
groupPolicy: "open",
surface: "Example groups",
openScope: "any member",
groupPolicyPath: "channels.example.groupPolicy",
groupAllowFromPath: "channels.example.groupAllowFrom",
}),
).toHaveLength(1);
});
it("collects route allowlist warning variants", () => {
const params = {
groupPolicy: "open" as const,
restrictSenders: {
surface: "Example groups",
openScope: "any member in allowed groups",
groupPolicyPath: "channels.example.groupPolicy",
groupAllowFromPath: "channels.example.groupAllowFrom",
},
noRouteAllowlist: {
surface: "Example groups",
routeAllowlistPath: "channels.example.groups",
routeScope: "group",
groupPolicyPath: "channels.example.groupPolicy",
groupAllowFromPath: "channels.example.groupAllowFrom",
},
};
expect(
collectOpenGroupPolicyRouteAllowlistWarnings({
...params,
routeAllowlistConfigured: true,
}),
).toEqual([buildOpenGroupPolicyRestrictSendersWarning(params.restrictSenders)]);
expect(
collectOpenGroupPolicyRouteAllowlistWarnings({
...params,
routeAllowlistConfigured: false,
}),
).toEqual([buildOpenGroupPolicyNoRouteAllowlistWarning(params.noRouteAllowlist)]);
});
it("collects configured-route warning variants", () => {
const params = {
groupPolicy: "open" as const,
configureRouteAllowlist: {
surface: "Example channels",
openScope: "any channel not explicitly denied",
groupPolicyPath: "channels.example.groupPolicy",
routeAllowlistPath: "channels.example.channels",
},
missingRouteAllowlist: {
surface: "Example channels",
openBehavior: "with no route allowlist; any channel can trigger (mention-gated)",
remediation:
'Set channels.example.groupPolicy="allowlist" and configure channels.example.channels',
},
};
expect(
collectOpenGroupPolicyConfiguredRouteWarnings({
...params,
routeAllowlistConfigured: true,
}),
).toEqual([buildOpenGroupPolicyConfigureRouteAllowlistWarning(params.configureRouteAllowlist)]);
expect(
collectOpenGroupPolicyConfiguredRouteWarnings({
...params,
routeAllowlistConfigured: false,
}),
).toEqual([buildOpenGroupPolicyWarning(params.missingRouteAllowlist)]);
});
});

View File

@@ -51,3 +51,44 @@ export function buildOpenGroupPolicyConfigureRouteAllowlistWarning(params: {
remediation: `Set ${params.groupPolicyPath}="allowlist" and configure ${params.routeAllowlistPath}`,
});
}
export function collectOpenGroupPolicyRestrictSendersWarnings(
params: Parameters<typeof buildOpenGroupPolicyRestrictSendersWarning>[0] & {
groupPolicy: "open" | "allowlist" | "disabled";
},
): string[] {
if (params.groupPolicy !== "open") {
return [];
}
return [buildOpenGroupPolicyRestrictSendersWarning(params)];
}
export function collectOpenGroupPolicyRouteAllowlistWarnings(params: {
groupPolicy: "open" | "allowlist" | "disabled";
routeAllowlistConfigured: boolean;
restrictSenders: Parameters<typeof buildOpenGroupPolicyRestrictSendersWarning>[0];
noRouteAllowlist: Parameters<typeof buildOpenGroupPolicyNoRouteAllowlistWarning>[0];
}): string[] {
if (params.groupPolicy !== "open") {
return [];
}
if (params.routeAllowlistConfigured) {
return [buildOpenGroupPolicyRestrictSendersWarning(params.restrictSenders)];
}
return [buildOpenGroupPolicyNoRouteAllowlistWarning(params.noRouteAllowlist)];
}
export function collectOpenGroupPolicyConfiguredRouteWarnings(params: {
groupPolicy: "open" | "allowlist" | "disabled";
routeAllowlistConfigured: boolean;
configureRouteAllowlist: Parameters<typeof buildOpenGroupPolicyConfigureRouteAllowlistWarning>[0];
missingRouteAllowlist: Parameters<typeof buildOpenGroupPolicyWarning>[0];
}): string[] {
if (params.groupPolicy !== "open") {
return [];
}
if (params.routeAllowlistConfigured) {
return [buildOpenGroupPolicyConfigureRouteAllowlistWarning(params.configureRouteAllowlist)];
}
return [buildOpenGroupPolicyWarning(params.missingRouteAllowlist)];
}

View File

@@ -536,6 +536,9 @@ export {
buildOpenGroupPolicyNoRouteAllowlistWarning,
buildOpenGroupPolicyRestrictSendersWarning,
buildOpenGroupPolicyWarning,
collectOpenGroupPolicyConfiguredRouteWarnings,
collectOpenGroupPolicyRestrictSendersWarnings,
collectOpenGroupPolicyRouteAllowlistWarnings,
} from "../channels/plugins/group-policy-warnings.js";
export {
buildAccountScopedDmSecurityPolicy,