security(irc): isolate group allowlist from DM pairing store

This commit is contained in:
Brian Mendonca
2026-02-24 21:31:05 -07:00
committed by Peter Steinberger
parent 0a58328217
commit d1bed505c5
2 changed files with 57 additions and 2 deletions

View File

@@ -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([]);
});
});

View File

@@ -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,
};