mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 14:00:47 +00:00
fix: guard provider-prefixed delivery targets
This commit is contained in:
@@ -129,6 +129,7 @@ export const bluebubblesPlugin: ChannelPlugin<ResolvedBlueBubblesAccount, BlueBu
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["bluebubbles"],
|
||||
normalizeTarget: normalizeBlueBubblesMessagingTarget,
|
||||
inferTargetChatType: ({ to }) => inferBlueBubblesTargetChatType(to),
|
||||
resolveOutboundSessionRoute: (params) => resolveBlueBubblesOutboundSessionRoute(params),
|
||||
|
||||
@@ -218,6 +218,7 @@ export const discordPlugin: ChannelPlugin<ResolvedDiscordAccount, DiscordProbe>
|
||||
],
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["discord"],
|
||||
normalizeTarget: normalizeDiscordMessagingTarget,
|
||||
resolveInboundConversation: ({ from, to, conversationId, isGroup }) =>
|
||||
resolveDiscordInboundConversation({ from, to, conversationId, isGroup }),
|
||||
|
||||
@@ -1154,6 +1154,7 @@ export const feishuPlugin: ChannelPlugin<ResolvedFeishuAccount, FeishuProbeResul
|
||||
setup: feishuSetupAdapter,
|
||||
setupWizard: feishuSetupWizard,
|
||||
messaging: {
|
||||
targetPrefixes: ["feishu", "lark"],
|
||||
normalizeTarget: (raw) => normalizeFeishuTarget(raw) ?? undefined,
|
||||
resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
|
||||
const directId = parseFeishuDirectConversationId(conversationId);
|
||||
|
||||
@@ -144,6 +144,7 @@ export const googlechatPlugin = createChatChannelPlugin({
|
||||
},
|
||||
groups: googlechatGroupsAdapter,
|
||||
messaging: {
|
||||
targetPrefixes: ["googlechat", "google-chat", "gchat"],
|
||||
normalizeTarget: normalizeGoogleChatTarget,
|
||||
targetResolver: {
|
||||
looksLikeId: (raw, normalized) => {
|
||||
|
||||
@@ -233,6 +233,7 @@ export const ircPlugin: ChannelPlugin<ResolvedIrcAccount, IrcProbe> = createChat
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["irc"],
|
||||
normalizeTarget: normalizeIrcMessagingTarget,
|
||||
targetResolver: {
|
||||
looksLikeId: looksLikeIrcTargetId,
|
||||
|
||||
@@ -42,6 +42,7 @@ export const linePlugin: ChannelPlugin<ResolvedLineAccount> = createChatChannelP
|
||||
resolveRequireMention: resolveLineGroupRequireMention,
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["line"],
|
||||
normalizeTarget: (target) => {
|
||||
const trimmed = target.trim();
|
||||
if (!trimmed) {
|
||||
|
||||
@@ -376,6 +376,7 @@ export const matrixPlugin: ChannelPlugin<ResolvedMatrixAccount, MatrixProbe> =
|
||||
}).map(projectMatrixConversationBinding),
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["matrix"],
|
||||
normalizeTarget: normalizeMatrixMessagingTarget,
|
||||
resolveInboundConversation: ({ to, conversationId, threadId }) =>
|
||||
resolveMatrixInboundConversation({ to, conversationId, threadId }),
|
||||
|
||||
@@ -306,6 +306,7 @@ export const mattermostPlugin: ChannelPlugin<ResolvedMattermostAccount> = create
|
||||
(await loadMattermostChannelRuntime()).listMattermostDirectoryPeers(params),
|
||||
}),
|
||||
messaging: {
|
||||
targetPrefixes: ["mattermost"],
|
||||
defaultMarkdownTableMode: "off",
|
||||
normalizeTarget: normalizeMattermostMessagingTarget,
|
||||
resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
|
||||
|
||||
@@ -450,6 +450,7 @@ export const msteamsPlugin: ChannelPlugin<ResolvedMSTeamsAccount, ProbeMSTeamsRe
|
||||
},
|
||||
setup: msteamsSetupAdapter,
|
||||
messaging: {
|
||||
targetPrefixes: ["msteams", "teams"],
|
||||
normalizeTarget: normalizeMSTeamsMessagingTarget,
|
||||
resolveOutboundSessionRoute: (params) => resolveMSTeamsOutboundSessionRoute(params),
|
||||
targetResolver: {
|
||||
|
||||
@@ -119,6 +119,7 @@ export const nextcloudTalkPlugin: ChannelPlugin<ResolvedNextcloudTalkAccount> =
|
||||
resolveToolPolicy: resolveNextcloudTalkGroupToolPolicy,
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["nextcloud-talk", "nc-talk", "nc"],
|
||||
normalizeTarget: normalizeNextcloudTalkMessagingTarget,
|
||||
resolveOutboundSessionRoute: (params) => resolveNextcloudTalkOutboundSessionRoute(params),
|
||||
targetResolver: {
|
||||
|
||||
@@ -118,6 +118,7 @@ export const nostrPlugin: ChannelPlugin<ResolvedNostrAccount> = createChatChanne
|
||||
}),
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["nostr"],
|
||||
normalizeTarget: (target) => {
|
||||
// Strip nostr: prefix if present
|
||||
const cleaned = target.trim().replace(/^nostr:/i, "");
|
||||
|
||||
@@ -99,6 +99,7 @@ export const qqbotPlugin: ChannelPlugin<ResolvedQQBotAccount> = {
|
||||
},
|
||||
approvalCapability: getQQBotApprovalCapability(),
|
||||
messaging: {
|
||||
targetPrefixes: ["qqbot"],
|
||||
/** Normalize common QQ Bot target formats into the canonical qqbot:... form. */
|
||||
normalizeTarget: coreNormalizeTarget,
|
||||
targetResolver: {
|
||||
|
||||
@@ -270,6 +270,7 @@ export const signalPlugin: ChannelPlugin<ResolvedSignalAccount, SignalProbe> =
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["signal"],
|
||||
normalizeTarget: normalizeSignalMessagingTarget,
|
||||
parseExplicitTarget: ({ raw }) => parseSignalExplicitTarget(raw),
|
||||
inferTargetChatType: ({ to }) => inferSignalTargetChatType(to),
|
||||
|
||||
@@ -385,6 +385,7 @@ export const slackPlugin: ChannelPlugin<ResolvedSlackAccount, SlackProbe> = crea
|
||||
resolveToolPolicy: resolveSlackGroupToolPolicy,
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["slack"],
|
||||
normalizeTarget: normalizeSlackMessagingTarget,
|
||||
resolveDeliveryTarget: ({ conversationId, parentConversationId }) => {
|
||||
const parent = parentConversationId?.trim();
|
||||
|
||||
@@ -345,6 +345,8 @@ describe("createSynologyChatPlugin", () => {
|
||||
it("normalizeTarget strips prefix and trims", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(plugin.messaging.normalizeTarget("synology-chat:123")).toBe("123");
|
||||
expect(plugin.messaging.normalizeTarget("synology_chat:123")).toBe("123");
|
||||
expect(plugin.messaging.normalizeTarget("synology:123")).toBe("123");
|
||||
expect(plugin.messaging.normalizeTarget(" 456 ")).toBe("456");
|
||||
expect(plugin.messaging.normalizeTarget("")).toBeUndefined();
|
||||
});
|
||||
@@ -353,6 +355,8 @@ describe("createSynologyChatPlugin", () => {
|
||||
const plugin = createSynologyChatPlugin();
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("12345")).toBe(true);
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("synology-chat:99")).toBe(true);
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("synology_chat:99")).toBe(true);
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("synology:99")).toBe(true);
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("notanumber")).toBe(false);
|
||||
expect(plugin.messaging.targetResolver.looksLikeId("")).toBe(false);
|
||||
});
|
||||
|
||||
@@ -155,6 +155,7 @@ type SynologyChatPlugin = Omit<
|
||||
}) => string[];
|
||||
};
|
||||
messaging: {
|
||||
targetPrefixes?: readonly string[];
|
||||
normalizeTarget: (target: string) => string | undefined;
|
||||
targetResolver: {
|
||||
looksLikeId: (id: string) => boolean;
|
||||
@@ -237,13 +238,14 @@ export function createSynologyChatPlugin(): SynologyChatPlugin {
|
||||
},
|
||||
approvalCapability: synologyChatApprovalAuth,
|
||||
messaging: {
|
||||
targetPrefixes: ["synology-chat", "synology_chat", "synology"],
|
||||
normalizeTarget: (target: string) => {
|
||||
const trimmed = target.trim();
|
||||
if (!trimmed) {
|
||||
return undefined;
|
||||
}
|
||||
// Strip common prefixes
|
||||
return trimmed.replace(/^synology[-_]?chat:/i, "").trim();
|
||||
return trimmed.replace(/^synology(?:[-_]?chat)?:/i, "").trim();
|
||||
},
|
||||
targetResolver: {
|
||||
looksLikeId: (id: string) => {
|
||||
@@ -252,7 +254,7 @@ export function createSynologyChatPlugin(): SynologyChatPlugin {
|
||||
return false;
|
||||
}
|
||||
// Synology Chat user IDs are numeric
|
||||
return /^\d+$/.test(trimmed) || /^synology[-_]?chat:/i.test(trimmed);
|
||||
return /^\d+$/.test(trimmed) || /^synology(?:[-_]?chat)?:/i.test(trimmed);
|
||||
},
|
||||
hint: "<userId>",
|
||||
},
|
||||
|
||||
@@ -130,7 +130,7 @@ function validateWebhookPath(value: string): string | undefined {
|
||||
}
|
||||
|
||||
function parseSynologyUserId(value: string): string | null {
|
||||
const cleaned = value.replace(/^synology-chat:/i, "").trim();
|
||||
const cleaned = value.replace(/^synology(?:[-_]?chat)?:/i, "").trim();
|
||||
return /^\d+$/.test(cleaned) ? cleaned : null;
|
||||
}
|
||||
|
||||
|
||||
@@ -691,6 +691,7 @@ export const telegramPlugin = createChatChannelPlugin({
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["telegram", "tg"],
|
||||
normalizeTarget: normalizeTelegramMessagingTarget,
|
||||
resolveInboundConversation: ({ to, conversationId, threadId }) =>
|
||||
resolveTelegramInboundConversation({ to, conversationId, threadId }),
|
||||
|
||||
@@ -96,6 +96,7 @@ export const tlonPlugin = createChatChannelPlugin({
|
||||
},
|
||||
doctor: tlonDoctor,
|
||||
messaging: {
|
||||
targetPrefixes: ["tlon"],
|
||||
normalizeTarget: (target) => {
|
||||
const parsed = parseTlonTarget(target);
|
||||
if (!parsed) {
|
||||
|
||||
@@ -101,6 +101,17 @@ describe("whatsappChannelOutbound", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("rejects non-WhatsApp provider-prefixed outbound targets", () => {
|
||||
const result = whatsappChannelOutbound.resolveTarget?.({
|
||||
to: "telegram:1234567890",
|
||||
allowFrom: [],
|
||||
mode: undefined,
|
||||
});
|
||||
|
||||
expect(result?.ok).toBe(false);
|
||||
expect(hoisted.sendMessageWhatsApp).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("preserves indentation for payload delivery", async () => {
|
||||
await whatsappChannelOutbound.sendPayload!({
|
||||
cfg: {},
|
||||
|
||||
@@ -111,6 +111,7 @@ export const whatsappPlugin: ChannelPlugin<ResolvedWhatsAppAccount> =
|
||||
},
|
||||
},
|
||||
messaging: {
|
||||
targetPrefixes: ["whatsapp"],
|
||||
normalizeTarget: normalizeWhatsAppMessagingTarget,
|
||||
resolveOutboundSessionRoute: (params) => resolveWhatsAppOutboundSessionRoute(params),
|
||||
parseExplicitTarget: ({ raw }) => parseWhatsAppExplicitTarget(raw),
|
||||
|
||||
@@ -4,6 +4,7 @@ import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtim
|
||||
const WHATSAPP_USER_JID_RE = /^(\d+)(?::\d+)?@s\.whatsapp\.net$/i;
|
||||
const WHATSAPP_LEGACY_USER_JID_RE = /^(\d+)@c\.us$/i;
|
||||
const WHATSAPP_LID_RE = /^(\d+)@lid$/i;
|
||||
const NON_WHATSAPP_PROVIDER_PREFIX_RE = /^[a-z][a-z0-9-]*:/i;
|
||||
|
||||
function stripWhatsAppTargetPrefixes(value: string): string {
|
||||
let candidate = value.trim();
|
||||
@@ -74,6 +75,9 @@ export function normalizeWhatsAppTarget(value: string): string | null {
|
||||
if (candidate.includes("@")) {
|
||||
return null;
|
||||
}
|
||||
if (NON_WHATSAPP_PROVIDER_PREFIX_RE.test(candidate)) {
|
||||
return null;
|
||||
}
|
||||
const normalized = normalizeE164(candidate);
|
||||
return normalized.length > 1 ? normalized : null;
|
||||
}
|
||||
|
||||
@@ -42,6 +42,13 @@ describe("normalizeWhatsAppTarget", () => {
|
||||
expect(normalizeWhatsAppTarget("abc@s.whatsapp.net")).toBeNull();
|
||||
});
|
||||
|
||||
it("rejects non-WhatsApp provider-prefixed phone-like targets", () => {
|
||||
expect(normalizeWhatsAppTarget("telegram:1234567890")).toBeNull();
|
||||
expect(normalizeWhatsAppTarget("tg:1234567890")).toBeNull();
|
||||
expect(normalizeWhatsAppTarget("sms:+15551234567")).toBeNull();
|
||||
expect(looksLikeWhatsAppTargetId("telegram:1234567890")).toBe(false);
|
||||
});
|
||||
|
||||
it("handles repeated prefixes", () => {
|
||||
expect(normalizeWhatsAppTarget("whatsapp:whatsapp:+1555")).toBe("+1555");
|
||||
expect(normalizeWhatsAppTarget("group:group:120@g.us")).toBeNull();
|
||||
|
||||
@@ -196,6 +196,7 @@ export const zaloPlugin: ChannelPlugin<ResolvedZaloAccount, ZaloProbeResult> =
|
||||
},
|
||||
actions: zaloMessageActions,
|
||||
messaging: {
|
||||
targetPrefixes: ["zalo", "zl"],
|
||||
normalizeTarget: normalizeZaloMessagingTarget,
|
||||
resolveOutboundSessionRoute: (params) => resolveZaloOutboundSessionRoute(params),
|
||||
targetResolver: {
|
||||
|
||||
@@ -370,6 +370,7 @@ export const zalouserOutboundAdapter = {
|
||||
};
|
||||
|
||||
export const zalouserMessagingAdapter = {
|
||||
targetPrefixes: ["zalouser", "zlu"],
|
||||
normalizeTarget: (raw: string) => normalizeZalouserTarget(raw),
|
||||
resolveOutboundSessionRoute: (
|
||||
params: Parameters<typeof resolveZalouserOutboundSessionRoute>[0],
|
||||
|
||||
Reference in New Issue
Block a user