diff --git a/extensions/irc/src/inbound.policy.test.ts b/extensions/irc/src/inbound.policy.test.ts new file mode 100644 index 00000000000..c5b6cdfac89 --- /dev/null +++ b/extensions/irc/src/inbound.policy.test.ts @@ -0,0 +1,34 @@ +import { describe, expect, it } from "vitest"; +import { __testing } from "./inbound.js"; + +describe("irc inbound policy", () => { + it("keeps DM allowlist merged with pairing-store entries", () => { + const resolved = __testing.resolveIrcEffectiveAllowlists({ + configAllowFrom: ["owner"], + configGroupAllowFrom: [], + storeAllowList: ["paired-user"], + }); + + expect(resolved.effectiveAllowFrom).toEqual(["owner", "paired-user"]); + }); + + it("does not grant group access from pairing-store when explicit groupAllowFrom exists", () => { + const resolved = __testing.resolveIrcEffectiveAllowlists({ + configAllowFrom: ["owner"], + configGroupAllowFrom: ["group-owner"], + storeAllowList: ["paired-user"], + }); + + expect(resolved.effectiveGroupAllowFrom).toEqual(["group-owner"]); + }); + + it("does not grant group access from pairing-store when groupAllowFrom is empty", () => { + const resolved = __testing.resolveIrcEffectiveAllowlists({ + configAllowFrom: ["owner"], + configGroupAllowFrom: [], + storeAllowList: ["paired-user"], + }); + + expect(resolved.effectiveGroupAllowFrom).toEqual([]); + }); +}); diff --git a/extensions/irc/src/inbound.ts b/extensions/irc/src/inbound.ts index 26d0aa85927..efb0b781d4a 100644 --- a/extensions/irc/src/inbound.ts +++ b/extensions/irc/src/inbound.ts @@ -31,6 +31,20 @@ const CHANNEL_ID = "irc" as const; const escapeIrcRegexLiteral = (value: string) => value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +function resolveIrcEffectiveAllowlists(params: { + configAllowFrom: string[]; + configGroupAllowFrom: string[]; + storeAllowList: string[]; +}): { + effectiveAllowFrom: string[]; + effectiveGroupAllowFrom: string[]; +} { + const effectiveAllowFrom = [...params.configAllowFrom, ...params.storeAllowList].filter(Boolean); + // Pairing-store entries are DM approvals and must not widen group sender authorization. + const effectiveGroupAllowFrom = [...params.configGroupAllowFrom].filter(Boolean); + return { effectiveAllowFrom, effectiveGroupAllowFrom }; +} + async function deliverIrcReply(params: { payload: OutboundReplyPayload; target: string; @@ -123,8 +137,11 @@ export async function handleIrcInbound(params: { const groupAllowFrom = directGroupAllowFrom.length > 0 ? directGroupAllowFrom : wildcardGroupAllowFrom; - const effectiveAllowFrom = [...configAllowFrom, ...storeAllowList].filter(Boolean); - const effectiveGroupAllowFrom = [...configGroupAllowFrom, ...storeAllowList].filter(Boolean); + const { effectiveAllowFrom, effectiveGroupAllowFrom } = resolveIrcEffectiveAllowlists({ + configAllowFrom, + configGroupAllowFrom, + storeAllowList, + }); const allowTextCommands = core.channel.commands.shouldHandleTextCommands({ cfg: config as OpenClawConfig, @@ -344,3 +361,7 @@ export async function handleIrcInbound(params: { }, }); } + +export const __testing = { + resolveIrcEffectiveAllowlists, +};