fix(outbound): return error instead of silently redirecting to allowList[0] (#13578)

This commit is contained in:
Marcus Castro
2026-02-13 01:20:03 -03:00
committed by GitHub
parent a43136c85e
commit 39ee708df6
11 changed files with 392 additions and 74 deletions

View File

@@ -0,0 +1,43 @@
import { describe, expect, it } from "vitest";
import { whatsappOutbound } from "./whatsapp.js";
describe("whatsappOutbound.resolveTarget", () => {
it("returns error when no target is provided even with allowFrom", () => {
const result = whatsappOutbound.resolveTarget?.({
to: undefined,
allowFrom: ["+15551234567"],
mode: "implicit",
});
expect(result).toEqual({
ok: false,
error: expect.any(Error),
});
});
it("returns error when implicit target is not in allowFrom", () => {
const result = whatsappOutbound.resolveTarget?.({
to: "+15550000000",
allowFrom: ["+15551234567"],
mode: "implicit",
});
expect(result).toEqual({
ok: false,
error: expect.any(Error),
});
});
it("keeps group JID targets even when allowFrom does not contain them", () => {
const result = whatsappOutbound.resolveTarget?.({
to: "120363401234567890@g.us",
allowFrom: ["+15551234567"],
mode: "implicit",
});
expect(result).toEqual({
ok: true,
to: "120363401234567890@g.us",
});
});
});

View File

@@ -23,15 +23,9 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
if (trimmed) {
const normalizedTo = normalizeWhatsAppTarget(trimmed);
if (!normalizedTo) {
if ((mode === "implicit" || mode === "heartbeat") && allowList.length > 0) {
return { ok: true, to: allowList[0] };
}
return {
ok: false,
error: missingTargetError(
"WhatsApp",
"<E.164|group JID> or channels.whatsapp.allowFrom[0]",
),
error: missingTargetError("WhatsApp", "<E.164|group JID>"),
};
}
if (isWhatsAppGroupJid(normalizedTo)) {
@@ -44,17 +38,17 @@ export const whatsappOutbound: ChannelOutboundAdapter = {
if (allowList.includes(normalizedTo)) {
return { ok: true, to: normalizedTo };
}
return { ok: true, to: allowList[0] };
return {
ok: false,
error: missingTargetError("WhatsApp", "<E.164|group JID>"),
};
}
return { ok: true, to: normalizedTo };
}
if (allowList.length > 0) {
return { ok: true, to: allowList[0] };
}
return {
ok: false,
error: missingTargetError("WhatsApp", "<E.164|group JID> or channels.whatsapp.allowFrom[0]"),
error: missingTargetError("WhatsApp", "<E.164|group JID>"),
};
},
sendText: async ({ to, text, accountId, deps, gifPlayback }) => {

View File

@@ -198,7 +198,7 @@ describe("resolveHeartbeatDeliveryTarget", () => {
});
});
it("applies allowFrom fallback for WhatsApp targets", () => {
it("rejects WhatsApp target not in allowFrom (no silent fallback)", () => {
const cfg: OpenClawConfig = {
agents: { defaults: { heartbeat: { target: "whatsapp", to: "+1999" } } },
channels: { whatsapp: { allowFrom: ["+1555", "+1666"] } },
@@ -209,9 +209,8 @@ describe("resolveHeartbeatDeliveryTarget", () => {
lastTo: "+1222",
};
expect(resolveHeartbeatDeliveryTarget({ cfg, entry })).toEqual({
channel: "whatsapp",
to: "+1555",
reason: "allowFrom-fallback",
channel: "none",
reason: "no-target",
accountId: undefined,
lastChannel: "whatsapp",
lastAccountId: undefined,

View File

@@ -16,7 +16,7 @@ describe("resolveOutboundTarget", () => {
);
});
it("falls back to whatsapp allowFrom via config", () => {
it("rejects whatsapp with empty target even when allowFrom configured", () => {
const cfg: OpenClawConfig = {
channels: { whatsapp: { allowFrom: ["+1555"] } },
};
@@ -26,7 +26,10 @@ describe("resolveOutboundTarget", () => {
cfg,
mode: "explicit",
});
expect(res).toEqual({ ok: true, to: "+1555" });
expect(res.ok).toBe(false);
if (!res.ok) {
expect(res.error.message).toContain("WhatsApp");
}
});
it.each([
@@ -49,18 +52,18 @@ describe("resolveOutboundTarget", () => {
expected: { ok: true as const, to: "120363401234567890@g.us" },
},
{
name: "falls back to whatsapp allowFrom",
name: "rejects whatsapp with empty target and allowFrom (no silent fallback)",
input: { channel: "whatsapp" as const, to: "", allowFrom: ["+1555"] },
expected: { ok: true as const, to: "+1555" },
expectedErrorIncludes: "WhatsApp",
},
{
name: "normalizes whatsapp allowFrom fallback targets",
name: "rejects whatsapp with empty target and prefixed allowFrom (no silent fallback)",
input: {
channel: "whatsapp" as const,
to: "",
allowFrom: ["whatsapp:(555) 123-4567"],
},
expected: { ok: true as const, to: "+5551234567" },
expectedErrorIncludes: "WhatsApp",
},
{
name: "rejects invalid whatsapp target",