fix: enforce explicit group auth boundaries across channels

This commit is contained in:
Peter Steinberger
2026-02-26 18:15:57 +01:00
parent d0d83a2020
commit 64de4b6d6a
20 changed files with 614 additions and 331 deletions

View File

@@ -27,7 +27,10 @@ import type { getChildLogger } from "../../../logging.js";
import { getAgentScopedMediaLocalRoots } from "../../../media/local-roots.js";
import { readChannelAllowFromStore } from "../../../pairing/pairing-store.js";
import type { resolveAgentRoute } from "../../../routing/resolve-route.js";
import { readStoreAllowFromForDmPolicy } from "../../../security/dm-policy-shared.js";
import {
readStoreAllowFromForDmPolicy,
resolveDmGroupAccessWithCommandGate,
} from "../../../security/dm-policy-shared.js";
import { jidToE164, normalizeE164 } from "../../../utils.js";
import { resolveWhatsAppAccount } from "../../accounts.js";
import { newConnectionId } from "../../reconnect.js";
@@ -49,15 +52,6 @@ export type GroupHistoryEntry = {
senderJid?: string;
};
function normalizeAllowFromE164(values: Array<string | number> | undefined): string[] {
const list = Array.isArray(values) ? values : [];
return list
.map((entry) => String(entry).trim())
.filter((entry) => entry && entry !== "*")
.map((entry) => normalizeE164(entry))
.filter((entry): entry is string => Boolean(entry));
}
async function resolveWhatsAppCommandAuthorized(params: {
cfg: ReturnType<typeof loadConfig>;
msg: WebInboundMsg;
@@ -77,38 +71,49 @@ async function resolveWhatsAppCommandAuthorized(params: {
const account = resolveWhatsAppAccount({ cfg: params.cfg, accountId: params.msg.accountId });
const dmPolicy = account.dmPolicy ?? "pairing";
const groupPolicy = account.groupPolicy ?? "allowlist";
const configuredAllowFrom = account.allowFrom ?? [];
const configuredGroupAllowFrom =
account.groupAllowFrom ?? (configuredAllowFrom.length > 0 ? configuredAllowFrom : undefined);
if (isGroup) {
if (!configuredGroupAllowFrom || configuredGroupAllowFrom.length === 0) {
return false;
}
if (configuredGroupAllowFrom.some((v) => String(v).trim() === "*")) {
return true;
}
return normalizeAllowFromE164(configuredGroupAllowFrom).includes(senderE164);
}
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "whatsapp",
dmPolicy,
readStore: (provider) => readChannelAllowFromStore(provider, process.env, params.msg.accountId),
});
const combinedAllowFrom = Array.from(
new Set([...(configuredAllowFrom ?? []), ...storeAllowFrom]),
);
const allowFrom =
combinedAllowFrom.length > 0
? combinedAllowFrom
const storeAllowFrom =
isGroup
? []
: await readStoreAllowFromForDmPolicy({
provider: "whatsapp",
dmPolicy,
readStore: (provider) =>
readChannelAllowFromStore(provider, process.env, params.msg.accountId),
});
const dmAllowFrom =
configuredAllowFrom.length > 0
? configuredAllowFrom
: params.msg.selfE164
? [params.msg.selfE164]
: [];
if (allowFrom.some((v) => String(v).trim() === "*")) {
return true;
}
return normalizeAllowFromE164(allowFrom).includes(senderE164);
const access = resolveDmGroupAccessWithCommandGate({
isGroup,
dmPolicy,
groupPolicy,
allowFrom: dmAllowFrom,
groupAllowFrom: configuredGroupAllowFrom,
storeAllowFrom,
isSenderAllowed: (allowEntries) => {
if (allowEntries.includes("*")) {
return true;
}
const normalizedEntries = allowEntries
.map((entry) => normalizeE164(String(entry)))
.filter((entry): entry is string => Boolean(entry));
return normalizedEntries.includes(senderE164);
},
command: {
useAccessGroups,
allowTextCommands: true,
hasControlCommand: true,
},
});
return access.commandAuthorized;
}
export async function processMessage(params: {