mirror of
https://github.com/moltbot/moltbot.git
synced 2026-03-07 22:44:16 +00:00
fix: fail closed missing provider group policy across message channels (#23367) (thanks @bmendonca3)
This commit is contained in:
@@ -18,6 +18,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
### Fixes
|
||||
|
||||
- Channels/Security: fail closed on missing provider group policy config by defaulting runtime group policy to `allowlist` (instead of inheriting `channels.defaults.groupPolicy`) when `channels.<provider>` is absent across message channels, and align runtime + security warnings/docs to the same fallback behavior (Slack, Discord, iMessage, Telegram, WhatsApp, Signal, LINE, Matrix, Mattermost, Google Chat, IRC, Nextcloud Talk, Feishu, and Zalo user flows; plus Discord message/native-command paths). (#23367) Thanks @bmendonca3.
|
||||
- CLI/Sessions: pass the configured sessions directory when resolving transcript paths in `agentCommand`, so custom `session.store` locations resume sessions reliably. Thanks @davidrudduck.
|
||||
- Gateway/Chat UI: strip inline reply/audio directive tags from non-streaming final webchat broadcasts (including `chat.inject`) while preserving empty-string message content when tags are the entire reply. (#23298) Thanks @SidQin-cyber.
|
||||
- Gateway/Restart: fix restart-loop edge cases by keeping `openclaw.mjs -> dist/entry.js` bootstrap detection explicit, reacquiring the gateway lock for in-process restart fallback paths, and tightening restart-loop regression coverage. (#23416) Thanks @jeffwnli.
|
||||
|
||||
@@ -425,7 +425,7 @@ Example:
|
||||
}
|
||||
```
|
||||
|
||||
If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="allowlist"` (with a warning in logs).
|
||||
If you only set `DISCORD_BOT_TOKEN` and do not create a `channels.discord` block, runtime fallback is `groupPolicy="allowlist"` (with a warning in logs), even if `channels.defaults.groupPolicy` is `open`.
|
||||
|
||||
</Tab>
|
||||
|
||||
|
||||
@@ -190,6 +190,7 @@ Notes:
|
||||
- Group DMs are controlled separately (`channels.discord.dm.*`, `channels.slack.dm.*`).
|
||||
- Telegram allowlist can match user IDs (`"123456789"`, `"telegram:123456789"`, `"tg:123456789"`) or usernames (`"@alice"` or `"alice"`); prefixes are case-insensitive.
|
||||
- Default is `groupPolicy: "allowlist"`; if your group allowlist is empty, group messages are blocked.
|
||||
- Runtime safety: when a provider block is completely missing (`channels.<provider>` absent), group policy falls back to a fail-closed mode (typically `allowlist`) instead of inheriting `channels.defaults.groupPolicy`.
|
||||
|
||||
Quick mental model (evaluation order for group messages):
|
||||
|
||||
|
||||
@@ -158,6 +158,7 @@ imsg send <handle> "test"
|
||||
Group sender allowlist: `channels.imessage.groupAllowFrom`.
|
||||
|
||||
Runtime fallback: if `groupAllowFrom` is unset, iMessage group sender checks fall back to `allowFrom` when available.
|
||||
Runtime note: if `channels.imessage` is completely missing, runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Mention gating for groups:
|
||||
|
||||
|
||||
@@ -118,6 +118,7 @@ Allowlists and policies:
|
||||
- `channels.line.groupPolicy`: `allowlist | open | disabled`
|
||||
- `channels.line.groupAllowFrom`: allowlisted LINE user IDs for groups
|
||||
- Per-group overrides: `channels.line.groups.<groupId>.allowFrom`
|
||||
- Runtime note: if `channels.line` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
LINE IDs are case-sensitive. Valid IDs look like:
|
||||
|
||||
|
||||
@@ -195,6 +195,7 @@ Notes:
|
||||
## Rooms (groups)
|
||||
|
||||
- Default: `channels.matrix.groupPolicy = "allowlist"` (mention-gated). Use `channels.defaults.groupPolicy` to override the default when unset.
|
||||
- Runtime note: if `channels.matrix` is completely missing, runtime falls back to `groupPolicy="allowlist"` for room checks (even if `channels.defaults.groupPolicy` is set).
|
||||
- Allowlist rooms with `channels.matrix.groups` (room IDs or aliases; names are resolved to IDs when directory search finds a single exact match):
|
||||
|
||||
```json5
|
||||
|
||||
@@ -103,6 +103,7 @@ Notes:
|
||||
- Default: `channels.mattermost.groupPolicy = "allowlist"` (mention-gated).
|
||||
- Allowlist senders with `channels.mattermost.groupAllowFrom` (user IDs or `@username`).
|
||||
- Open channels: `channels.mattermost.groupPolicy="open"` (mention-gated).
|
||||
- Runtime note: if `channels.mattermost` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## Targets for outbound delivery
|
||||
|
||||
|
||||
@@ -195,6 +195,7 @@ Groups:
|
||||
|
||||
- `channels.signal.groupPolicy = open | allowlist | disabled`.
|
||||
- `channels.signal.groupAllowFrom` controls who can trigger in groups when `allowlist` is set.
|
||||
- Runtime note: if `channels.signal` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group checks (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
## How it works (behavior)
|
||||
|
||||
|
||||
@@ -165,7 +165,7 @@ For actions/directory reads, user token can be preferred when configured. For wr
|
||||
|
||||
Channel allowlist lives under `channels.slack.channels`.
|
||||
|
||||
Runtime note: if `channels.slack` is completely missing (env-only setup) and `channels.defaults.groupPolicy` is unset, runtime falls back to `groupPolicy="allowlist"` and logs a warning.
|
||||
Runtime note: if `channels.slack` is completely missing (env-only setup), runtime falls back to `groupPolicy="allowlist"` and logs a warning (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Name/ID resolution:
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ curl "https://api.telegram.org/bot<bot_token>/getUpdates"
|
||||
|
||||
`groupAllowFrom` is used for group sender filtering. If not set, Telegram falls back to `allowFrom`.
|
||||
`groupAllowFrom` entries must be numeric Telegram user IDs.
|
||||
Runtime note: if `channels.telegram` is completely missing, runtime falls back to `groupPolicy="allowlist"` for group policy evaluation (even if `channels.defaults.groupPolicy` is set).
|
||||
|
||||
Example: allow any member in one specific group:
|
||||
|
||||
|
||||
@@ -171,7 +171,7 @@ OpenClaw recommends running WhatsApp on a separate number when possible. (The ch
|
||||
- if `groupAllowFrom` is unset, runtime falls back to `allowFrom` when available
|
||||
- sender allowlists are evaluated before mention/reply activation
|
||||
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is effectively `open`.
|
||||
Note: if no `channels.whatsapp` block exists at all, runtime group-policy fallback is `allowlist` (with a warning log), even if `channels.defaults.groupPolicy` is set.
|
||||
|
||||
</Tab>
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
resolveDefaultDiscordAccountId,
|
||||
resolveDiscordGroupRequireMention,
|
||||
resolveDiscordGroupToolPolicy,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelPlugin,
|
||||
@@ -131,7 +132,13 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount> = {
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const warnings: string[] = [];
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.discord !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
const guildEntries = account.config.guilds ?? {};
|
||||
const guildsConfigured = Object.keys(guildEntries).length > 0;
|
||||
const channelAllowlistConfigured = guildsConfigured;
|
||||
|
||||
@@ -2,10 +2,11 @@ import type { ClawdbotConfig, RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
buildAgentMediaPayload,
|
||||
buildPendingHistoryContextFromMap,
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
clearHistoryEntriesIfEnabled,
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
type HistoryEntry,
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
resolveRuntimeGroupPolicy,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { resolveFeishuAccount } from "./accounts.js";
|
||||
import { createFeishuClient } from "./client.js";
|
||||
@@ -77,6 +78,7 @@ const senderNameCache = new Map<string, { name: string; expireAt: number }>();
|
||||
// Key: appId or "default", Value: timestamp of last notification
|
||||
const permissionErrorNotifiedAt = new Map<string, number>();
|
||||
const PERMISSION_ERROR_COOLDOWN_MS = 5 * 60 * 1000; // 5 minutes
|
||||
const groupPolicyFallbackWarningShown = new Set<string>();
|
||||
|
||||
type SenderNameResult = {
|
||||
name?: string;
|
||||
@@ -563,7 +565,20 @@ export async function handleFeishuMessage(params: {
|
||||
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
|
||||
|
||||
if (isGroup) {
|
||||
const groupPolicy = feishuCfg?.groupPolicy ?? "open";
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.feishu !== undefined,
|
||||
groupPolicy: feishuCfg?.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied && !groupPolicyFallbackWarningShown.has(account.accountId)) {
|
||||
groupPolicyFallbackWarningShown.add(account.accountId);
|
||||
log(
|
||||
'feishu: channels.feishu is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
const groupAllowFrom = feishuCfg?.groupAllowFrom ?? [];
|
||||
// DEBUG: log(`feishu[${account.accountId}]: groupPolicy=${groupPolicy}`);
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createDefaultChannelRuntimeState,
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveRuntimeGroupPolicy,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import {
|
||||
resolveFeishuAccount,
|
||||
@@ -227,7 +228,13 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount> = {
|
||||
const defaultGroupPolicy = (
|
||||
cfg.channels as Record<string, { groupPolicy?: string }> | undefined
|
||||
)?.defaults?.groupPolicy;
|
||||
const groupPolicy = feishuCfg?.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.feishu !== undefined,
|
||||
groupPolicy: feishuCfg?.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") return [];
|
||||
return [
|
||||
`- Feishu[${account.accountId}] groups: groupPolicy="open" allows any member to trigger (mention-gated). Set channels.feishu.groupPolicy="allowlist" + channels.feishu.groupAllowFrom to restrict senders.`,
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveChannelMediaMaxBytes,
|
||||
resolveGoogleChatGroupRequireMention,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelDock,
|
||||
type ChannelMessageActionAdapter,
|
||||
@@ -199,7 +200,13 @@ export const googlechatPlugin: ChannelPlugin<ResolvedGoogleChatAccount> = {
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const warnings: string[] = [];
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.googlechat !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy === "open") {
|
||||
warnings.push(
|
||||
`- Google Chat spaces: groupPolicy="open" allows any space to trigger (mention-gated). Set channels.googlechat.groupPolicy="allowlist" and configure channels.googlechat.groups.`,
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
readJsonBodyWithLimit,
|
||||
registerWebhookTarget,
|
||||
rejectNonPostWebhookRequest,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveSingleWebhookTargetAsync,
|
||||
resolveWebhookPath,
|
||||
resolveWebhookTargets,
|
||||
@@ -67,6 +68,7 @@ function logVerbose(core: GoogleChatCoreRuntime, runtime: GoogleChatRuntimeEnv,
|
||||
}
|
||||
|
||||
const warnedDeprecatedUsersEmailAllowFrom = new Set<string>();
|
||||
const warnedMissingProviderGroupPolicy = new Set<string>();
|
||||
function warnDeprecatedUsersEmailEntries(
|
||||
core: GoogleChatCoreRuntime,
|
||||
runtime: GoogleChatRuntimeEnv,
|
||||
@@ -427,7 +429,21 @@ async function processMessageWithPipeline(params: {
|
||||
}
|
||||
|
||||
const defaultGroupPolicy = config.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: config.channels?.googlechat !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied && !warnedMissingProviderGroupPolicy.has(account.accountId)) {
|
||||
warnedMissingProviderGroupPolicy.add(account.accountId);
|
||||
logVerbose(
|
||||
core,
|
||||
runtime,
|
||||
'googlechat: channels.googlechat is missing; defaulting groupPolicy to "allowlist" (space messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
const groupConfigResolved = resolveGroupConfig({
|
||||
groupId: spaceId,
|
||||
groupName: space.displayName ?? null,
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
resolveIMessageAccount,
|
||||
resolveIMessageGroupRequireMention,
|
||||
resolveIMessageGroupToolPolicy,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelPlugin,
|
||||
type ResolvedIMessageAccount,
|
||||
@@ -98,7 +99,13 @@ export const imessagePlugin: ChannelPlugin<ResolvedIMessageAccount> = {
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.imessage !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
formatPairingApproveHint,
|
||||
getChatChannelMeta,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
deleteAccountFromConfigSection,
|
||||
type ChannelPlugin,
|
||||
@@ -135,7 +136,13 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = {
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const warnings: string[] = [];
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.irc !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy === "open") {
|
||||
warnings.push(
|
||||
'- IRC channels: groupPolicy="open" allows all channels and senders (mention-gated). Prefer channels.irc.groupPolicy="allowlist" with channels.irc.groups.',
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
createReplyPrefixOptions,
|
||||
logInboundDrop,
|
||||
resolveControlCommandGate,
|
||||
resolveRuntimeGroupPolicy,
|
||||
type OpenClawConfig,
|
||||
type RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk";
|
||||
@@ -19,6 +20,7 @@ import { sendMessageIrc } from "./send.js";
|
||||
import type { CoreConfig, IrcInboundMessage } from "./types.js";
|
||||
|
||||
const CHANNEL_ID = "irc" as const;
|
||||
const warnedMissingProviderGroupPolicy = new Set<string>();
|
||||
|
||||
const escapeIrcRegexLiteral = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||
|
||||
@@ -85,7 +87,19 @@ export async function handleIrcInbound(params: {
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const defaultGroupPolicy = config.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: config.channels?.irc !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied && !warnedMissingProviderGroupPolicy.has(account.accountId)) {
|
||||
warnedMissingProviderGroupPolicy.add(account.accountId);
|
||||
runtime.log?.(
|
||||
'irc: channels.irc is missing; defaulting groupPolicy to "allowlist" (channel messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
|
||||
const configAllowFrom = normalizeIrcAllowlist(account.config.allowFrom);
|
||||
const configGroupAllowFrom = normalizeIrcAllowlist(account.config.groupAllowFrom);
|
||||
|
||||
@@ -3,6 +3,7 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
LineConfigSchema,
|
||||
processLineMessage,
|
||||
resolveRuntimeGroupPolicy,
|
||||
type ChannelPlugin,
|
||||
type ChannelStatusIssue,
|
||||
type OpenClawConfig,
|
||||
@@ -163,7 +164,13 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = {
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = (cfg.channels?.defaults as { groupPolicy?: string } | undefined)
|
||||
?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.line !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
formatPairingApproveHint,
|
||||
normalizeAccountId,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelPlugin,
|
||||
} from "openclaw/plugin-sdk";
|
||||
@@ -170,7 +171,13 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount> = {
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = (cfg as CoreConfig).channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: (cfg as CoreConfig).channels?.matrix !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { format } from "node:util";
|
||||
import { mergeAllowlist, summarizeMapping, type RuntimeEnv } from "openclaw/plugin-sdk";
|
||||
import {
|
||||
mergeAllowlist,
|
||||
resolveRuntimeGroupPolicy,
|
||||
summarizeMapping,
|
||||
type RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { resolveMatrixTargets } from "../../resolve-targets.js";
|
||||
import { getMatrixRuntime } from "../../runtime.js";
|
||||
import type { CoreConfig, ReplyToMode } from "../../types.js";
|
||||
@@ -243,7 +248,20 @@ export async function monitorMatrixProvider(opts: MonitorMatrixOpts = {}): Promi
|
||||
|
||||
const mentionRegexes = core.channel.mentions.buildMentionRegexes(cfg);
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicyRaw = accountConfig.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy: groupPolicyRaw, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy(
|
||||
{
|
||||
providerConfigPresent: cfg.channels?.matrix !== undefined,
|
||||
groupPolicy: accountConfig.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
},
|
||||
);
|
||||
if (providerMissingFallbackApplied) {
|
||||
logVerboseMessage(
|
||||
'matrix: channels.matrix is missing; defaulting groupPolicy to "allowlist" (room messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
const groupPolicy = allowlistOnly && groupPolicyRaw === "open" ? "allowlist" : groupPolicyRaw;
|
||||
const replyToMode = opts.replyToMode ?? accountConfig.replyToMode ?? "off";
|
||||
const threadReplies = accountConfig.threadReplies ?? "inbound";
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
formatPairingApproveHint,
|
||||
migrateBaseNameToDefaultAccount,
|
||||
normalizeAccountId,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelMessageActionAdapter,
|
||||
type ChannelMessageActionName,
|
||||
@@ -229,7 +230,13 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = {
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.mattermost !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ import {
|
||||
DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
recordPendingHistoryEntryIfEnabled,
|
||||
resolveControlCommandGate,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveChannelMediaMaxBytes,
|
||||
type HistoryEntry,
|
||||
} from "openclaw/plugin-sdk";
|
||||
@@ -242,6 +243,19 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
cfg.messages?.groupChat?.historyLimit ?? DEFAULT_GROUP_HISTORY_LIMIT,
|
||||
);
|
||||
const channelHistories = new Map<string, HistoryEntry[]>();
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.mattermost !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied) {
|
||||
logVerboseMessage(
|
||||
'mattermost: channels.mattermost is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
|
||||
const fetchWithAuth: FetchLike = (input, init) => {
|
||||
const headers = new Headers(init?.headers);
|
||||
@@ -375,8 +389,6 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
senderId;
|
||||
const rawText = post.message?.trim() || "";
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
|
||||
const configGroupAllowFrom = normalizeAllowList(account.config.groupAllowFrom ?? []);
|
||||
const storeAllowFrom = normalizeAllowList(
|
||||
@@ -887,8 +899,6 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
|
||||
}
|
||||
}
|
||||
} else if (kind) {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
if (groupPolicy === "disabled") {
|
||||
logVerboseMessage(`mattermost: drop reaction (groupPolicy=disabled channel=${channelId})`);
|
||||
return;
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
DEFAULT_ACCOUNT_ID,
|
||||
MSTeamsConfigSchema,
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveRuntimeGroupPolicy,
|
||||
} from "openclaw/plugin-sdk";
|
||||
import { listMSTeamsDirectoryGroupsLive, listMSTeamsDirectoryPeersLive } from "./directory-live.js";
|
||||
import { msteamsOnboardingAdapter } from "./onboarding.js";
|
||||
@@ -128,7 +129,13 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount> = {
|
||||
security: {
|
||||
collectWarnings: ({ cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = cfg.channels?.msteams?.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.msteams !== undefined,
|
||||
groupPolicy: cfg.channels?.msteams?.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
deleteAccountFromConfigSection,
|
||||
formatPairingApproveHint,
|
||||
normalizeAccountId,
|
||||
resolveRuntimeGroupPolicy,
|
||||
setAccountEnabledInConfigSection,
|
||||
type ChannelPlugin,
|
||||
type OpenClawConfig,
|
||||
@@ -129,7 +130,14 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent:
|
||||
(cfg.channels as Record<string, unknown> | undefined)?.["nextcloud-talk"] !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import {
|
||||
createReplyPrefixOptions,
|
||||
logInboundDrop,
|
||||
resolveControlCommandGate,
|
||||
resolveRuntimeGroupPolicy,
|
||||
type OpenClawConfig,
|
||||
type RuntimeEnv,
|
||||
} from "openclaw/plugin-sdk";
|
||||
@@ -20,6 +21,7 @@ import { sendMessageNextcloudTalk } from "./send.js";
|
||||
import type { CoreConfig, GroupPolicy, NextcloudTalkInboundMessage } from "./types.js";
|
||||
|
||||
const CHANNEL_ID = "nextcloud-talk" as const;
|
||||
const warnedMissingProviderGroupPolicy = new Set<string>();
|
||||
|
||||
async function deliverNextcloudTalkReply(params: {
|
||||
payload: { text?: string; mediaUrls?: string[]; mediaUrl?: string; replyToId?: string };
|
||||
@@ -84,12 +86,26 @@ export async function handleNextcloudTalkInbound(params: {
|
||||
statusSink?.({ lastInboundAt: message.timestamp });
|
||||
|
||||
const dmPolicy = account.config.dmPolicy ?? "pairing";
|
||||
const defaultGroupPolicy = (config.channels as Record<string, unknown> | undefined)?.defaults as
|
||||
| { groupPolicy?: string }
|
||||
| undefined;
|
||||
const groupPolicy = (account.config.groupPolicy ??
|
||||
defaultGroupPolicy?.groupPolicy ??
|
||||
"allowlist") as GroupPolicy;
|
||||
const defaultGroupPolicy = (
|
||||
(config.channels as Record<string, unknown> | undefined)?.defaults as
|
||||
| { groupPolicy?: string }
|
||||
| undefined
|
||||
)?.groupPolicy as GroupPolicy | undefined;
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent:
|
||||
((config.channels as Record<string, unknown> | undefined)?.["nextcloud-talk"] ??
|
||||
undefined) !== undefined,
|
||||
groupPolicy: account.config.groupPolicy as GroupPolicy | undefined,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied && !warnedMissingProviderGroupPolicy.has(account.accountId)) {
|
||||
warnedMissingProviderGroupPolicy.add(account.accountId);
|
||||
runtime.log?.(
|
||||
'nextcloud-talk: channels.nextcloud-talk is missing; defaulting groupPolicy to "allowlist" (room messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
|
||||
const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
|
||||
const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
PAIRING_APPROVED_MESSAGE,
|
||||
resolveChannelMediaMaxBytes,
|
||||
resolveDefaultSignalAccountId,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveSignalAccount,
|
||||
setAccountEnabledInConfigSection,
|
||||
signalOnboardingAdapter,
|
||||
@@ -124,7 +125,13 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount> = {
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.signal !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
resolveDefaultSlackAccountId,
|
||||
resolveSlackAccount,
|
||||
resolveSlackReplyToMode,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveSlackGroupRequireMention,
|
||||
resolveSlackGroupToolPolicy,
|
||||
buildSlackThreadingToolContext,
|
||||
@@ -151,7 +152,13 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount> = {
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const warnings: string[] = [];
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.slack !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
const channelAllowlistConfigured =
|
||||
Boolean(account.config.channels) && Object.keys(account.config.channels ?? {}).length > 0;
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
parseTelegramReplyToMessageId,
|
||||
parseTelegramThreadId,
|
||||
resolveDefaultTelegramAccountId,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveTelegramAccount,
|
||||
resolveTelegramGroupRequireMention,
|
||||
resolveTelegramGroupToolPolicy,
|
||||
@@ -196,7 +197,13 @@ export const telegramPlugin: ChannelPlugin<ResolvedTelegramAccount, TelegramProb
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.telegram !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
readStringParam,
|
||||
resolveDefaultWhatsAppAccountId,
|
||||
resolveWhatsAppOutboundTarget,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveWhatsAppAccount,
|
||||
resolveWhatsAppGroupRequireMention,
|
||||
resolveWhatsAppGroupToolPolicy,
|
||||
@@ -143,7 +144,13 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> = {
|
||||
},
|
||||
collectWarnings: ({ account, cfg }) => {
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.whatsapp !== undefined,
|
||||
groupPolicy: account.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (groupPolicy !== "open") {
|
||||
return [];
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import type { OpenClawConfig, MarkdownTableMode, RuntimeEnv } from "openclaw/plu
|
||||
import {
|
||||
createReplyPrefixOptions,
|
||||
mergeAllowlist,
|
||||
resolveRuntimeGroupPolicy,
|
||||
resolveSenderCommandAuthorization,
|
||||
summarizeMapping,
|
||||
} from "openclaw/plugin-sdk";
|
||||
@@ -178,7 +179,20 @@ async function processMessage(
|
||||
const chatId = threadId;
|
||||
|
||||
const defaultGroupPolicy = config.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "open";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: config.channels?.zalouser !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied) {
|
||||
logVerbose(
|
||||
core,
|
||||
runtime,
|
||||
'zalouser: channels.zalouser is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
const groups = account.config.groups ?? {};
|
||||
if (isGroup) {
|
||||
if (groupPolicy === "disabled") {
|
||||
|
||||
@@ -447,7 +447,7 @@ export const zalouserOnboardingAdapter: ChannelOnboardingAdapter = {
|
||||
const accessConfig = await promptChannelAccessConfig({
|
||||
prompter,
|
||||
label: "Zalo groups",
|
||||
currentPolicy: account.config.groupPolicy ?? "open",
|
||||
currentPolicy: account.config.groupPolicy ?? "allowlist",
|
||||
currentEntries: Object.keys(account.config.groups ?? {}),
|
||||
placeholder: "Family, Work, 123456789",
|
||||
updatePrompt: Boolean(account.config.groups),
|
||||
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
createInboundDebouncer,
|
||||
resolveInboundDebounceMs,
|
||||
} from "../../auto-reply/inbound-debounce.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import { danger } from "../../globals.js";
|
||||
import type { DiscordMessageEvent, DiscordMessageHandler } from "./listeners.js";
|
||||
import { preflightDiscordMessage } from "./message-handler.preflight.js";
|
||||
@@ -23,7 +24,13 @@ type DiscordMessageHandlerParams = Omit<
|
||||
export function createDiscordMessageHandler(
|
||||
params: DiscordMessageHandlerParams,
|
||||
): DiscordMessageHandler {
|
||||
const groupPolicy = params.discordConfig?.groupPolicy ?? "open";
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.cfg.channels?.discord !== undefined,
|
||||
groupPolicy: params.discordConfig?.groupPolicy,
|
||||
defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
const ackReactionScope = params.cfg.messages?.ackReactionScope ?? "group-mentions";
|
||||
const debounceMs = resolveInboundDebounceMs({ cfg: params.cfg, channel: "discord" });
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ import type { ReplyPayload } from "../../auto-reply/types.js";
|
||||
import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command-gating.js";
|
||||
import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
|
||||
import type { OpenClawConfig, loadConfig } from "../../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import { loadSessionStore, resolveStorePath } from "../../config/sessions.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { createSubsystemLogger } from "../../logging/subsystem.js";
|
||||
@@ -1329,8 +1330,15 @@ async function dispatchDiscordCommandInteraction(params: {
|
||||
const channelAllowlistConfigured =
|
||||
Boolean(guildInfo?.channels) && Object.keys(guildInfo?.channels ?? {}).length > 0;
|
||||
const channelAllowed = channelConfig?.allowed !== false;
|
||||
const { groupPolicy } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.discord !== undefined,
|
||||
groupPolicy: discordConfig?.groupPolicy,
|
||||
defaultGroupPolicy: cfg.channels?.defaults?.groupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
const allowByPolicy = isDiscordGroupAllowedByPolicy({
|
||||
groupPolicy: discordConfig?.groupPolicy ?? "open",
|
||||
groupPolicy,
|
||||
guildAllowlisted: Boolean(guildInfo),
|
||||
channelAllowlistConfigured,
|
||||
channelAllowed,
|
||||
|
||||
@@ -26,4 +26,13 @@ describe("resolveDiscordRuntimeGroupPolicy", () => {
|
||||
expect(resolved.groupPolicy).toBe("disabled");
|
||||
expect(resolved.providerMissingFallbackApplied).toBe(false);
|
||||
});
|
||||
|
||||
it("ignores explicit global defaults when provider config is missing", () => {
|
||||
const resolved = __testing.resolveDiscordRuntimeGroupPolicy({
|
||||
providerConfigPresent: false,
|
||||
defaultGroupPolicy: "open",
|
||||
});
|
||||
expect(resolved.groupPolicy).toBe("allowlist");
|
||||
expect(resolved.providerMissingFallbackApplied).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import {
|
||||
} from "../../config/commands.js";
|
||||
import type { OpenClawConfig, ReplyToMode } from "../../config/config.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import type { GroupPolicy } from "../../config/types.base.js";
|
||||
import { danger, logVerbose, shouldLogVerbose, warn } from "../../globals.js";
|
||||
import { formatErrorMessage } from "../../infra/errors.js";
|
||||
@@ -179,15 +180,13 @@ function resolveDiscordRuntimeGroupPolicy(params: {
|
||||
groupPolicy: GroupPolicy;
|
||||
providerMissingFallbackApplied: boolean;
|
||||
} {
|
||||
const groupPolicy =
|
||||
params.groupPolicy ??
|
||||
params.defaultGroupPolicy ??
|
||||
(params.providerConfigPresent ? "open" : "allowlist");
|
||||
const providerMissingFallbackApplied =
|
||||
!params.providerConfigPresent &&
|
||||
params.groupPolicy === undefined &&
|
||||
params.defaultGroupPolicy === undefined;
|
||||
return { groupPolicy, providerMissingFallbackApplied };
|
||||
return resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.providerConfigPresent,
|
||||
groupPolicy: params.groupPolicy,
|
||||
defaultGroupPolicy: params.defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
}
|
||||
|
||||
async function deployDiscordCommands(params: {
|
||||
@@ -265,20 +264,22 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
|
||||
|
||||
const runtime: RuntimeEnv = opts.runtime ?? createNonExitingRuntime();
|
||||
|
||||
const discordCfg = account.config;
|
||||
const rawDiscordCfg = account.config;
|
||||
const discordRootThreadBindings = cfg.channels?.discord?.threadBindings;
|
||||
const discordAccountThreadBindings =
|
||||
cfg.channels?.discord?.accounts?.[account.accountId]?.threadBindings;
|
||||
const discordRestFetch = resolveDiscordRestFetch(discordCfg.proxy, runtime);
|
||||
const dmConfig = discordCfg.dm;
|
||||
let guildEntries = discordCfg.guilds;
|
||||
const discordRestFetch = resolveDiscordRestFetch(rawDiscordCfg.proxy, runtime);
|
||||
const dmConfig = rawDiscordCfg.dm;
|
||||
let guildEntries = rawDiscordCfg.guilds;
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const providerConfigPresent = cfg.channels?.discord !== undefined;
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveDiscordRuntimeGroupPolicy({
|
||||
providerConfigPresent,
|
||||
groupPolicy: discordCfg.groupPolicy,
|
||||
groupPolicy: rawDiscordCfg.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
});
|
||||
const discordCfg =
|
||||
rawDiscordCfg.groupPolicy === groupPolicy ? rawDiscordCfg : { ...rawDiscordCfg, groupPolicy };
|
||||
if (providerMissingFallbackApplied) {
|
||||
runtime.log?.(
|
||||
warn(
|
||||
|
||||
@@ -16,8 +16,10 @@ import { createReplyDispatcher } from "../../auto-reply/reply/reply-dispatcher.j
|
||||
import { createReplyPrefixOptions } from "../../channels/reply-prefix.js";
|
||||
import { recordInboundSession } from "../../channels/session.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import { readSessionUpdatedAt, resolveStorePath } from "../../config/sessions.js";
|
||||
import { danger, logVerbose, shouldLogVerbose } from "../../globals.js";
|
||||
import type { GroupPolicy } from "../../config/types.base.js";
|
||||
import { danger, logVerbose, shouldLogVerbose, warn } from "../../globals.js";
|
||||
import { normalizeScpRemoteHost } from "../../infra/scp-host.js";
|
||||
import { waitForTransportReady } from "../../infra/transport-ready.js";
|
||||
import { mediaKindFromMime } from "../../media/constants.js";
|
||||
@@ -120,6 +122,23 @@ class SentMessageCache {
|
||||
}
|
||||
}
|
||||
|
||||
function resolveIMessageRuntimeGroupPolicy(params: {
|
||||
providerConfigPresent: boolean;
|
||||
groupPolicy?: GroupPolicy;
|
||||
defaultGroupPolicy?: GroupPolicy;
|
||||
}): {
|
||||
groupPolicy: GroupPolicy;
|
||||
providerMissingFallbackApplied: boolean;
|
||||
} {
|
||||
return resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.providerConfigPresent,
|
||||
groupPolicy: params.groupPolicy,
|
||||
defaultGroupPolicy: params.defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
}
|
||||
|
||||
export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): Promise<void> {
|
||||
const runtime = resolveRuntime(opts);
|
||||
const cfg = opts.config ?? loadConfig();
|
||||
@@ -144,7 +163,18 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
(imessageCfg.allowFrom && imessageCfg.allowFrom.length > 0 ? imessageCfg.allowFrom : []),
|
||||
);
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = imessageCfg.groupPolicy ?? defaultGroupPolicy ?? "open";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveIMessageRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.imessage !== undefined,
|
||||
groupPolicy: imessageCfg.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
});
|
||||
if (providerMissingFallbackApplied) {
|
||||
runtime.log?.(
|
||||
warn(
|
||||
'imessage: channels.imessage is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
),
|
||||
);
|
||||
}
|
||||
const dmPolicy = imessageCfg.dmPolicy ?? "pairing";
|
||||
const includeAttachments = opts.includeAttachments ?? imessageCfg.includeAttachments ?? false;
|
||||
const mediaMaxBytes = (opts.mediaMaxMb ?? imessageCfg.mediaMaxMb ?? 16) * 1024 * 1024;
|
||||
@@ -508,3 +538,7 @@ export async function monitorIMessageProvider(opts: MonitorIMessageOpts = {}): P
|
||||
await client.stop();
|
||||
}
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
resolveIMessageRuntimeGroupPolicy,
|
||||
};
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
PostbackEvent,
|
||||
} from "@line/bot-sdk";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../config/runtime-group-policy.js";
|
||||
import { danger, logVerbose } from "../globals.js";
|
||||
import { resolvePairingIdLabel } from "../pairing/pairing-labels.js";
|
||||
import { buildPairingReply } from "../pairing/pairing-messages.js";
|
||||
@@ -40,6 +41,8 @@ export interface LineHandlerContext {
|
||||
processMessage: (ctx: LineInboundContext) => Promise<void>;
|
||||
}
|
||||
|
||||
let lineGroupPolicyFallbackWarned = false;
|
||||
|
||||
function resolveLineGroupConfig(params: {
|
||||
config: ResolvedLineAccount["config"];
|
||||
groupId?: string;
|
||||
@@ -133,7 +136,19 @@ async function shouldProcessLineEvent(
|
||||
dmPolicy,
|
||||
});
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.line !== undefined,
|
||||
groupPolicy: account.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied && !lineGroupPolicyFallbackWarned) {
|
||||
lineGroupPolicyFallbackWarned = true;
|
||||
logVerbose(
|
||||
'line: channels.line is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
|
||||
if (isGroup) {
|
||||
if (groupConfig?.enabled === false) {
|
||||
|
||||
@@ -132,6 +132,10 @@ export type {
|
||||
MSTeamsReplyStyle,
|
||||
MSTeamsTeamConfig,
|
||||
} from "../config/types.js";
|
||||
export {
|
||||
resolveRuntimeGroupPolicy,
|
||||
type RuntimeGroupPolicyResolution,
|
||||
} from "../config/runtime-group-policy.js";
|
||||
export {
|
||||
DiscordConfigSchema,
|
||||
GoogleChatConfigSchema,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { DEFAULT_GROUP_HISTORY_LIMIT, type HistoryEntry } from "../auto-reply/re
|
||||
import type { ReplyPayload } from "../auto-reply/types.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadConfig } from "../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../config/runtime-group-policy.js";
|
||||
import type { SignalReactionNotificationMode } from "../config/types.js";
|
||||
import { waitForTransportReady } from "../infra/transport-ready.js";
|
||||
import { saveMediaBuffer } from "../media/store.js";
|
||||
@@ -345,7 +346,18 @@ export async function monitorSignalProvider(opts: MonitorSignalOpts = {}): Promi
|
||||
: []),
|
||||
);
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = accountInfo.config.groupPolicy ?? defaultGroupPolicy ?? "allowlist";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.signal !== undefined,
|
||||
groupPolicy: accountInfo.config.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "allowlist",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
if (providerMissingFallbackApplied) {
|
||||
runtime.log?.(
|
||||
'signal: channels.signal is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
const reactionMode = accountInfo.config.reactionNotifications ?? "own";
|
||||
const reactionAllowlist = normalizeAllowList(accountInfo.config.reactionAllowlist);
|
||||
const mediaMaxBytes = (opts.mediaMaxMb ?? accountInfo.config.mediaMaxMb ?? 8) * 1024 * 1024;
|
||||
|
||||
@@ -18,12 +18,12 @@ describe("resolveSlackRuntimeGroupPolicy", () => {
|
||||
expect(resolved.providerMissingFallbackApplied).toBe(false);
|
||||
});
|
||||
|
||||
it("respects explicit global defaults", () => {
|
||||
it("ignores explicit global defaults when provider config is missing", () => {
|
||||
const resolved = __testing.resolveSlackRuntimeGroupPolicy({
|
||||
providerConfigPresent: false,
|
||||
defaultGroupPolicy: "open",
|
||||
});
|
||||
expect(resolved.groupPolicy).toBe("open");
|
||||
expect(resolved.providerMissingFallbackApplied).toBe(false);
|
||||
expect(resolved.groupPolicy).toBe("allowlist");
|
||||
expect(resolved.providerMissingFallbackApplied).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
summarizeMapping,
|
||||
} from "../../channels/allowlists/resolve-utils.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import type { SessionScope } from "../../config/sessions.js";
|
||||
import type { GroupPolicy } from "../../config/types.base.js";
|
||||
import { warn } from "../../globals.js";
|
||||
@@ -50,15 +51,13 @@ function resolveSlackRuntimeGroupPolicy(params: {
|
||||
groupPolicy: GroupPolicy;
|
||||
providerMissingFallbackApplied: boolean;
|
||||
} {
|
||||
const groupPolicy =
|
||||
params.groupPolicy ??
|
||||
params.defaultGroupPolicy ??
|
||||
(params.providerConfigPresent ? "open" : "allowlist");
|
||||
const providerMissingFallbackApplied =
|
||||
!params.providerConfigPresent &&
|
||||
params.groupPolicy === undefined &&
|
||||
params.defaultGroupPolicy === undefined;
|
||||
return { groupPolicy, providerMissingFallbackApplied };
|
||||
return resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.providerConfigPresent,
|
||||
groupPolicy: params.groupPolicy,
|
||||
defaultGroupPolicy: params.defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
}
|
||||
|
||||
function parseApiAppIdFromAppToken(raw?: string) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import type { ChannelGroupPolicy } from "../config/group-policy.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../config/runtime-group-policy.js";
|
||||
import type {
|
||||
TelegramAccountConfig,
|
||||
TelegramGroupConfig,
|
||||
@@ -72,6 +73,19 @@ export type TelegramGroupPolicyAccessResult =
|
||||
groupPolicy: "open" | "disabled" | "allowlist";
|
||||
};
|
||||
|
||||
export const resolveTelegramRuntimeGroupPolicy = (params: {
|
||||
providerConfigPresent: boolean;
|
||||
groupPolicy?: TelegramAccountConfig["groupPolicy"];
|
||||
defaultGroupPolicy?: TelegramAccountConfig["groupPolicy"];
|
||||
}) =>
|
||||
resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.providerConfigPresent,
|
||||
groupPolicy: params.groupPolicy,
|
||||
defaultGroupPolicy: params.defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
|
||||
export const evaluateTelegramGroupPolicyAccess = (params: {
|
||||
isGroup: boolean;
|
||||
chatId: string | number;
|
||||
@@ -90,20 +104,21 @@ export const evaluateTelegramGroupPolicyAccess = (params: {
|
||||
requireSenderForAllowlistAuthorization: boolean;
|
||||
checkChatAllowlist: boolean;
|
||||
}): TelegramGroupPolicyAccessResult => {
|
||||
const { groupPolicy: runtimeFallbackPolicy } = resolveTelegramRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.cfg.channels?.telegram !== undefined,
|
||||
groupPolicy: params.telegramCfg.groupPolicy,
|
||||
defaultGroupPolicy: params.cfg.channels?.defaults?.groupPolicy,
|
||||
});
|
||||
const fallbackPolicy =
|
||||
firstDefined(
|
||||
params.telegramCfg.groupPolicy,
|
||||
params.cfg.channels?.defaults?.groupPolicy,
|
||||
"open",
|
||||
) ?? "open";
|
||||
firstDefined(params.telegramCfg.groupPolicy, params.cfg.channels?.defaults?.groupPolicy) ??
|
||||
runtimeFallbackPolicy;
|
||||
const groupPolicy = params.useTopicAndGroupOverrides
|
||||
? (firstDefined(
|
||||
params.topicConfig?.groupPolicy,
|
||||
params.groupConfig?.groupPolicy,
|
||||
params.telegramCfg.groupPolicy,
|
||||
params.cfg.channels?.defaults?.groupPolicy,
|
||||
"open",
|
||||
) ?? "open")
|
||||
) ?? runtimeFallbackPolicy)
|
||||
: fallbackPolicy;
|
||||
|
||||
if (!params.isGroup || !params.enforcePolicy) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { resolveRuntimeGroupPolicy } from "../../config/runtime-group-policy.js";
|
||||
import { logVerbose } from "../../globals.js";
|
||||
import { buildPairingReply } from "../../pairing/pairing-messages.js";
|
||||
import {
|
||||
@@ -17,6 +18,23 @@ export type InboundAccessControlResult = {
|
||||
|
||||
const PAIRING_REPLY_HISTORY_GRACE_MS = 30_000;
|
||||
|
||||
function resolveWhatsAppRuntimeGroupPolicy(params: {
|
||||
providerConfigPresent: boolean;
|
||||
groupPolicy?: "open" | "allowlist" | "disabled";
|
||||
defaultGroupPolicy?: "open" | "allowlist" | "disabled";
|
||||
}): {
|
||||
groupPolicy: "open" | "allowlist" | "disabled";
|
||||
providerMissingFallbackApplied: boolean;
|
||||
} {
|
||||
return resolveRuntimeGroupPolicy({
|
||||
providerConfigPresent: params.providerConfigPresent,
|
||||
groupPolicy: params.groupPolicy,
|
||||
defaultGroupPolicy: params.defaultGroupPolicy,
|
||||
configuredFallbackPolicy: "open",
|
||||
missingProviderFallbackPolicy: "allowlist",
|
||||
});
|
||||
}
|
||||
|
||||
export async function checkInboundAccessControl(params: {
|
||||
accountId: string;
|
||||
from: string;
|
||||
@@ -82,7 +100,16 @@ export async function checkInboundAccessControl(params: {
|
||||
// - "disabled": block all group messages entirely
|
||||
// - "allowlist": only allow group messages from senders in groupAllowFrom/allowFrom
|
||||
const defaultGroupPolicy = cfg.channels?.defaults?.groupPolicy;
|
||||
const groupPolicy = account.groupPolicy ?? defaultGroupPolicy ?? "open";
|
||||
const { groupPolicy, providerMissingFallbackApplied } = resolveWhatsAppRuntimeGroupPolicy({
|
||||
providerConfigPresent: cfg.channels?.whatsapp !== undefined,
|
||||
groupPolicy: account.groupPolicy,
|
||||
defaultGroupPolicy,
|
||||
});
|
||||
if (providerMissingFallbackApplied) {
|
||||
logVerbose(
|
||||
'whatsapp: channels.whatsapp is missing; defaulting groupPolicy to "allowlist" (group messages blocked until explicitly configured).',
|
||||
);
|
||||
}
|
||||
if (params.group && groupPolicy === "disabled") {
|
||||
logVerbose("Blocked group message (groupPolicy: disabled)");
|
||||
return {
|
||||
@@ -191,3 +218,7 @@ export async function checkInboundAccessControl(params: {
|
||||
resolvedAccountId: account.accountId,
|
||||
};
|
||||
}
|
||||
|
||||
export const __testing = {
|
||||
resolveWhatsAppRuntimeGroupPolicy,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user