From a189394590070f9f657632624169769cf865c233 Mon Sep 17 00:00:00 2001 From: chaoliang yan Date: Fri, 17 Apr 2026 12:42:55 +1000 Subject: [PATCH] fix: guard WhatsApp setup prompt values (#67895) (thanks @lawrence3699) * fix(whatsapp): guard setup prompt values * fix(whatsapp): preserve allowFrom invalid input details * fix: guard WhatsApp setup prompt values (#67895) (thanks @lawrence3699) --------- Co-authored-by: lawrence3699 Co-authored-by: Ayaan Zaidi --- CHANGELOG.md | 1 + extensions/whatsapp/src/channel.setup.test.ts | 25 +++++++++++++++++++ extensions/whatsapp/src/setup-finalize.ts | 18 ++++++++++--- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9f55c2510dd..fb93c8e317c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Docs: https://docs.openclaw.ai - Onboarding/non-interactive: preserve existing gateway auth tokens during re-onboard so active local gateway clients are not disconnected by an implicit token rotation. (#67821) Thanks @BKF-Gitty. - OpenAI Codex/Responses: unify native Responses API capability detection so Codex OAuth requests emit the required `store: false` field on the native Responses path. (#67918) Thanks @obviyus. +- WhatsApp/setup: guard personal-phone and allowlist prompt values so setup fails with clear validation errors instead of crashing on undefined prompt text. (#67895) Thanks @lawrence3699. ## 2026.4.15 diff --git a/extensions/whatsapp/src/channel.setup.test.ts b/extensions/whatsapp/src/channel.setup.test.ts index 5bc5dc4d439..4021c7eec77 100644 --- a/extensions/whatsapp/src/channel.setup.test.ts +++ b/extensions/whatsapp/src/channel.setup.test.ts @@ -169,6 +169,19 @@ describe("whatsapp setup wizard", () => { expectWhatsAppAllowlistModeSetup(result.cfg); }); + it("throws a user-facing error instead of crashing when allowlist input is undefined", async () => { + const harness = createSeparatePhoneHarness({ + selectValues: ["separate", "allowlist", "list"], + }); + harness.text.mockResolvedValueOnce(undefined as never); + + await expect( + runConfigureWithHarness({ + harness, + }), + ).rejects.toThrow("Invalid WhatsApp allowFrom list"); + }); + it("enables allowlist self-chat mode for personal-phone setup", async () => { hoisted.pathExists.mockResolvedValue(true); const harness = createWhatsAppPersonalPhoneHarness(createQueuedWizardPrompter); @@ -180,6 +193,18 @@ describe("whatsapp setup wizard", () => { expectWhatsAppPersonalPhoneSetup(result.cfg); }); + it("throws a user-facing error instead of crashing when personal-phone input is undefined", async () => { + hoisted.pathExists.mockResolvedValue(true); + const harness = createWhatsAppPersonalPhoneHarness(createQueuedWizardPrompter); + harness.text.mockResolvedValueOnce(undefined as never); + + await expect( + runConfigureWithHarness({ + harness, + }), + ).rejects.toThrow("Invalid WhatsApp owner number"); + }); + it("forces wildcard allowFrom for open policy without allowFrom follow-up prompts", async () => { hoisted.pathExists.mockResolvedValue(true); const harness = createSeparatePhoneHarness({ diff --git a/extensions/whatsapp/src/setup-finalize.ts b/extensions/whatsapp/src/setup-finalize.ts index 71ccbdcc28a..5efcce46393 100644 --- a/extensions/whatsapp/src/setup-finalize.ts +++ b/extensions/whatsapp/src/setup-finalize.ts @@ -23,6 +23,10 @@ type SetupRuntime = Parameters>[0][" type WhatsAppConfig = NonNullable["whatsapp"]>; type WhatsAppAccountConfig = NonNullable[string]>; +function trimPromptText(value: string | null | undefined): string { + return value?.trim() ?? ""; +} + function mergeWhatsAppConfig( cfg: OpenClawConfig, accountId: string, @@ -124,7 +128,7 @@ async function promptWhatsAppOwnerAllowFrom(params: { placeholder: "+15555550123", initialValue: existingAllowFrom[0], validate: (value) => { - const raw = value.trim(); + const raw = trimPromptText(value); if (!raw) { return "Required"; } @@ -136,7 +140,7 @@ async function promptWhatsAppOwnerAllowFrom(params: { }, }); - const normalized = normalizeE164(entry.trim()); + const normalized = normalizeE164(trimPromptText(entry)); if (!normalized) { throw new Error("Invalid WhatsApp owner number (expected E.164 after validation)."); } @@ -311,7 +315,7 @@ async function promptWhatsAppDmAccess(params: { message: "Allowed sender numbers (comma-separated, E.164)", placeholder: "+15555550123, +447700900123", validate: (value) => { - const raw = value.trim(); + const raw = trimPromptText(value); if (!raw) { return "Required"; } @@ -326,7 +330,13 @@ async function promptWhatsAppDmAccess(params: { }, }); - const parsed = parseWhatsAppAllowFromEntries(allowRaw); + const parsed = parseWhatsAppAllowFromEntries(trimPromptText(allowRaw)); + if (parsed.invalidEntry) { + throw new Error(`Invalid number: ${parsed.invalidEntry}`); + } + if (parsed.entries.length === 0) { + throw new Error("Invalid WhatsApp allowFrom list (expected at least one E.164 number)."); + } return setWhatsAppAllowFrom(next, accountId, parsed.entries); }