diff --git a/clawnify-patches/whatsapp-clawnify-tools.patch b/clawnify-patches/whatsapp-clawnify-tools.patch index f4fc0886657b8..b4c5221f8c76b 100644 --- a/clawnify-patches/whatsapp-clawnify-tools.patch +++ b/clawnify-patches/whatsapp-clawnify-tools.patch @@ -1744,6 +1744,71 @@ index cbc2ea2ad4..40acbc7040 100644 const allowListRaw = normalizeStringEntries(params.allowFrom ?? []); const hasWildcard = allowListRaw.includes("*"); const allowList = allowListRaw +diff --git a/extensions/whatsapp/src/runtime-group-policy.test.ts b/extensions/whatsapp/src/runtime-group-policy.test.ts +new file mode 100644 +index 0000000000..d2fbe23dc0 +--- /dev/null ++++ b/extensions/whatsapp/src/runtime-group-policy.test.ts +@@ -0,0 +1,34 @@ ++import { describe, expect, it } from "vitest"; ++import { resolveWhatsAppRuntimeGroupPolicy } from "./runtime-group-policy.js"; ++ ++describe("resolveWhatsAppRuntimeGroupPolicy (Clawnify fail-closed)", () => { ++ it("falls back to allowlist when channels.whatsapp exists without groupPolicy", () => { ++ // Regression: the open fallback admitted every group when groupPolicy was absent. ++ const result = resolveWhatsAppRuntimeGroupPolicy({ providerConfigPresent: true }); ++ expect(result.groupPolicy).toBe("allowlist"); ++ }); ++ ++ it("falls back to allowlist when channels.whatsapp is missing entirely", () => { ++ const result = resolveWhatsAppRuntimeGroupPolicy({ providerConfigPresent: false }); ++ expect(result.groupPolicy).toBe("allowlist"); ++ expect(result.providerMissingFallbackApplied).toBe(true); ++ }); ++ ++ it("honors an explicit groupPolicy", () => { ++ for (const groupPolicy of ["open", "allowlist", "disabled"] as const) { ++ const result = resolveWhatsAppRuntimeGroupPolicy({ ++ providerConfigPresent: true, ++ groupPolicy, ++ }); ++ expect(result.groupPolicy).toBe(groupPolicy); ++ } ++ }); ++ ++ it("honors channels.defaults.groupPolicy over the fallback", () => { ++ const result = resolveWhatsAppRuntimeGroupPolicy({ ++ providerConfigPresent: true, ++ defaultGroupPolicy: "open", ++ }); ++ expect(result.groupPolicy).toBe("open"); ++ }); ++}); +diff --git a/extensions/whatsapp/src/runtime-group-policy.ts b/extensions/whatsapp/src/runtime-group-policy.ts +index 15ddee1266..116b4188c3 100644 +--- a/extensions/whatsapp/src/runtime-group-policy.ts ++++ b/extensions/whatsapp/src/runtime-group-policy.ts +@@ -1,5 +1,5 @@ + // Whatsapp plugin module implements runtime group policy behavior. +-import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy"; ++import { resolveAllowlistProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy"; + + export function resolveWhatsAppRuntimeGroupPolicy(params: { + providerConfigPresent: boolean; +@@ -9,7 +9,12 @@ export function resolveWhatsAppRuntimeGroupPolicy(params: { + groupPolicy: "open" | "allowlist" | "disabled"; + providerMissingFallbackApplied: boolean; + } { +- return resolveOpenProviderRuntimeGroupPolicy({ ++ // Fail closed: a configured channels.whatsapp without an explicit groupPolicy ++ // should not admit every group the linked number is a member of. Fall back to ++ // "allowlist" instead of "open" — group inbound stays blocked until groups are ++ // explicitly configured (groupAllowFrom senders still pass, and explicit ++ // groupPolicy values are honored unchanged). ++ return resolveAllowlistProviderRuntimeGroupPolicy({ + providerConfigPresent: params.providerConfigPresent, + groupPolicy: params.groupPolicy, + defaultGroupPolicy: params.defaultGroupPolicy, diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index ed3a798ae6..b06632f96d 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts diff --git a/extensions/whatsapp/src/runtime-group-policy.test.ts b/extensions/whatsapp/src/runtime-group-policy.test.ts new file mode 100644 index 0000000000000..d2fbe23dc083f --- /dev/null +++ b/extensions/whatsapp/src/runtime-group-policy.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { resolveWhatsAppRuntimeGroupPolicy } from "./runtime-group-policy.js"; + +describe("resolveWhatsAppRuntimeGroupPolicy (Clawnify fail-closed)", () => { + it("falls back to allowlist when channels.whatsapp exists without groupPolicy", () => { + // Regression: the open fallback admitted every group when groupPolicy was absent. + const result = resolveWhatsAppRuntimeGroupPolicy({ providerConfigPresent: true }); + expect(result.groupPolicy).toBe("allowlist"); + }); + + it("falls back to allowlist when channels.whatsapp is missing entirely", () => { + const result = resolveWhatsAppRuntimeGroupPolicy({ providerConfigPresent: false }); + expect(result.groupPolicy).toBe("allowlist"); + expect(result.providerMissingFallbackApplied).toBe(true); + }); + + it("honors an explicit groupPolicy", () => { + for (const groupPolicy of ["open", "allowlist", "disabled"] as const) { + const result = resolveWhatsAppRuntimeGroupPolicy({ + providerConfigPresent: true, + groupPolicy, + }); + expect(result.groupPolicy).toBe(groupPolicy); + } + }); + + it("honors channels.defaults.groupPolicy over the fallback", () => { + const result = resolveWhatsAppRuntimeGroupPolicy({ + providerConfigPresent: true, + defaultGroupPolicy: "open", + }); + expect(result.groupPolicy).toBe("open"); + }); +}); diff --git a/extensions/whatsapp/src/runtime-group-policy.ts b/extensions/whatsapp/src/runtime-group-policy.ts index ddf2ef86fe687..2536b91bafa5c 100644 --- a/extensions/whatsapp/src/runtime-group-policy.ts +++ b/extensions/whatsapp/src/runtime-group-policy.ts @@ -1,4 +1,4 @@ -import { resolveOpenProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy"; +import { resolveAllowlistProviderRuntimeGroupPolicy } from "openclaw/plugin-sdk/runtime-group-policy"; export function resolveWhatsAppRuntimeGroupPolicy(params: { providerConfigPresent: boolean; @@ -8,7 +8,12 @@ export function resolveWhatsAppRuntimeGroupPolicy(params: { groupPolicy: "open" | "allowlist" | "disabled"; providerMissingFallbackApplied: boolean; } { - return resolveOpenProviderRuntimeGroupPolicy({ + // Fail closed: a configured channels.whatsapp without an explicit groupPolicy + // should not admit every group the linked number is a member of. Fall back to + // "allowlist" instead of "open" — group inbound stays blocked until groups are + // explicitly configured (groupAllowFrom senders still pass, and explicit + // groupPolicy values are honored unchanged). + return resolveAllowlistProviderRuntimeGroupPolicy({ providerConfigPresent: params.providerConfigPresent, groupPolicy: params.groupPolicy, defaultGroupPolicy: params.defaultGroupPolicy,