diff --git a/extensions/bluebubbles/src/targets.test.ts b/extensions/bluebubbles/src/targets.test.ts index cb159b1fb..c5b4109eb 100644 --- a/extensions/bluebubbles/src/targets.test.ts +++ b/extensions/bluebubbles/src/targets.test.ts @@ -1,5 +1,6 @@ import { describe, expect, it } from "vitest"; import { + isAllowedBlueBubblesSender, looksLikeBlueBubblesTargetId, normalizeBlueBubblesMessagingTarget, parseBlueBubblesTarget, @@ -181,3 +182,21 @@ describe("parseBlueBubblesAllowTarget", () => { }); }); }); + +describe("isAllowedBlueBubblesSender", () => { + it("denies when allowFrom is empty", () => { + const allowed = isAllowedBlueBubblesSender({ + allowFrom: [], + sender: "+15551234567", + }); + expect(allowed).toBe(false); + }); + + it("allows wildcard entries", () => { + const allowed = isAllowedBlueBubblesSender({ + allowFrom: ["*"], + sender: "+15551234567", + }); + expect(allowed).toBe(true); + }); +}); diff --git a/src/plugin-sdk/allow-from.test.ts b/src/plugin-sdk/allow-from.test.ts index cc69376c5..62fa4a137 100644 --- a/src/plugin-sdk/allow-from.test.ts +++ b/src/plugin-sdk/allow-from.test.ts @@ -37,6 +37,18 @@ describe("isAllowedParsedChatSender", () => { expect(allowed).toBe(false); }); + it("can explicitly allow when allowFrom is empty", () => { + const allowed = isAllowedParsedChatSender({ + allowFrom: [], + sender: "+15551234567", + emptyAllowFrom: "allow", + normalizeSender: (sender) => sender, + parseAllowTarget, + }); + + expect(allowed).toBe(true); + }); + it("allows wildcard entries", () => { const allowed = isAllowedParsedChatSender({ allowFrom: ["*"], diff --git a/src/plugin-sdk/allow-from.ts b/src/plugin-sdk/allow-from.ts index 39ef27787..df3ab305b 100644 --- a/src/plugin-sdk/allow-from.ts +++ b/src/plugin-sdk/allow-from.ts @@ -21,12 +21,15 @@ export function isAllowedParsedChatSender chatId?: number | null; chatGuid?: string | null; chatIdentifier?: string | null; + emptyAllowFrom?: "deny" | "allow"; normalizeSender: (sender: string) => string; parseAllowTarget: (entry: string) => TParsed; }): boolean { const allowFrom = params.allowFrom.map((entry) => String(entry).trim()); if (allowFrom.length === 0) { - return false; + // Fail closed by default. Callers can opt into legacy "empty = allow all" + // behavior explicitly when a surface intentionally treats an empty list as open. + return params.emptyAllowFrom === "allow"; } if (allowFrom.includes("*")) { return true;