fix(security): keep DM pairing allowlists out of group auth

This commit is contained in:
Peter Steinberger
2026-02-26 12:58:06 +01:00
parent d08dafb08f
commit 8bdda7a651
15 changed files with 194 additions and 54 deletions

View File

@@ -0,0 +1,37 @@
import { describe, expect, it } from "vitest";
import { resolveMattermostEffectiveAllowFromLists } from "./monitor.js";
describe("mattermost monitor authz", () => {
it("keeps DM allowlist merged with pairing-store entries", () => {
const resolved = resolveMattermostEffectiveAllowFromLists({
dmPolicy: "pairing",
allowFrom: ["@trusted-user"],
groupAllowFrom: ["@group-owner"],
storeAllowFrom: ["user:attacker"],
});
expect(resolved.effectiveAllowFrom).toEqual(["trusted-user", "attacker"]);
});
it("uses explicit groupAllowFrom without pairing-store inheritance", () => {
const resolved = resolveMattermostEffectiveAllowFromLists({
dmPolicy: "pairing",
allowFrom: ["@trusted-user"],
groupAllowFrom: ["@group-owner"],
storeAllowFrom: ["user:attacker"],
});
expect(resolved.effectiveGroupAllowFrom).toEqual(["group-owner"]);
});
it("does not inherit pairing-store entries into group allowlist", () => {
const resolved = resolveMattermostEffectiveAllowFromLists({
dmPolicy: "pairing",
allowFrom: ["@trusted-user"],
storeAllowFrom: ["user:attacker"],
});
expect(resolved.effectiveAllowFrom).toEqual(["trusted-user", "attacker"]);
expect(resolved.effectiveGroupAllowFrom).toEqual(["trusted-user"]);
});
});

View File

@@ -18,6 +18,7 @@ import {
isDangerousNameMatchingEnabled,
resolveControlCommandGate,
resolveDmGroupAccessWithLists,
resolveEffectiveAllowFromLists,
resolveAllowlistProviderRuntimeGroupPolicy,
resolveDefaultGroupPolicy,
resolveChannelMediaMaxBytes,
@@ -150,6 +151,23 @@ function normalizeAllowList(entries: Array<string | number>): string[] {
return Array.from(new Set(normalized));
}
export function resolveMattermostEffectiveAllowFromLists(params: {
allowFrom?: Array<string | number> | null;
groupAllowFrom?: Array<string | number> | null;
storeAllowFrom?: Array<string | number> | null;
dmPolicy?: string | null;
}): {
effectiveAllowFrom: string[];
effectiveGroupAllowFrom: string[];
} {
return resolveEffectiveAllowFromLists({
allowFrom: normalizeAllowList(params.allowFrom ?? []),
groupAllowFrom: normalizeAllowList(params.groupAllowFrom ?? []),
storeAllowFrom: normalizeAllowList(params.storeAllowFrom ?? []),
dmPolicy: params.dmPolicy,
});
}
function isSenderAllowed(params: {
senderId: string;
senderName?: string;
@@ -400,20 +418,18 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
senderId;
const rawText = post.message?.trim() || "";
const dmPolicy = account.config.dmPolicy ?? "pairing";
const configAllowFrom = normalizeAllowList(account.config.allowFrom ?? []);
const configGroupAllowFrom = normalizeAllowList(account.config.groupAllowFrom ?? []);
const storeAllowFrom = normalizeAllowList(
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
);
const effectiveAllowFrom = Array.from(new Set([...configAllowFrom, ...storeAllowFrom]));
const effectiveGroupAllowFrom = Array.from(
new Set([
...(configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom),
...storeAllowFrom,
]),
);
const { effectiveAllowFrom, effectiveGroupAllowFrom } =
resolveMattermostEffectiveAllowFromLists({
dmPolicy,
allowFrom: account.config.allowFrom,
groupAllowFrom: account.config.groupAllowFrom,
storeAllowFrom,
});
const allowTextCommands = core.channel.commands.shouldHandleTextCommands({
cfg,
surface: "mattermost",