From 0a58328217af94579e2c7d46069931d8ce99db97 Mon Sep 17 00:00:00 2001 From: Brian Mendonca Date: Tue, 24 Feb 2026 21:35:31 -0700 Subject: [PATCH] security(nextcloud-talk): isolate group allowlist from pairing-store entries --- .../nextcloud-talk/src/inbound.authz.test.ts | 81 +++++++++++++++++++ extensions/nextcloud-talk/src/inbound.ts | 2 +- 2 files changed, 82 insertions(+), 1 deletion(-) create mode 100644 extensions/nextcloud-talk/src/inbound.authz.test.ts diff --git a/extensions/nextcloud-talk/src/inbound.authz.test.ts b/extensions/nextcloud-talk/src/inbound.authz.test.ts new file mode 100644 index 000000000..88a655ec4 --- /dev/null +++ b/extensions/nextcloud-talk/src/inbound.authz.test.ts @@ -0,0 +1,81 @@ +import type { PluginRuntime, RuntimeEnv } from "openclaw/plugin-sdk"; +import { describe, expect, it, vi } from "vitest"; +import type { ResolvedNextcloudTalkAccount } from "./accounts.js"; +import { handleNextcloudTalkInbound } from "./inbound.js"; +import { setNextcloudTalkRuntime } from "./runtime.js"; +import type { CoreConfig, NextcloudTalkInboundMessage } from "./types.js"; + +describe("nextcloud-talk inbound authz", () => { + it("does not treat DM pairing-store entries as group allowlist entries", async () => { + const readAllowFromStore = vi.fn(async () => ["attacker"]); + const buildMentionRegexes = vi.fn(() => [/@openclaw/i]); + + setNextcloudTalkRuntime({ + channel: { + pairing: { + readAllowFromStore, + }, + commands: { + shouldHandleTextCommands: () => false, + }, + text: { + hasControlCommand: () => false, + }, + mentions: { + buildMentionRegexes, + matchesMentionPatterns: () => false, + }, + }, + } as unknown as PluginRuntime); + + const message: NextcloudTalkInboundMessage = { + messageId: "m-1", + roomToken: "room-1", + roomName: "Room 1", + senderId: "attacker", + senderName: "Attacker", + text: "hello", + mediaType: "text/plain", + timestamp: Date.now(), + isGroupChat: true, + }; + + const account: ResolvedNextcloudTalkAccount = { + accountId: "default", + enabled: true, + baseUrl: "", + secret: "", + secretSource: "none", + config: { + dmPolicy: "pairing", + allowFrom: [], + groupPolicy: "allowlist", + groupAllowFrom: [], + }, + }; + + const config: CoreConfig = { + channels: { + "nextcloud-talk": { + dmPolicy: "pairing", + allowFrom: [], + groupPolicy: "allowlist", + groupAllowFrom: [], + }, + }, + }; + + await handleNextcloudTalkInbound({ + message, + account, + config, + runtime: { + log: vi.fn(), + error: vi.fn(), + } as unknown as RuntimeEnv, + }); + + expect(readAllowFromStore).toHaveBeenCalledWith("nextcloud-talk"); + expect(buildMentionRegexes).not.toHaveBeenCalled(); + }); +}); diff --git a/extensions/nextcloud-talk/src/inbound.ts b/extensions/nextcloud-talk/src/inbound.ts index dcef6aa93..526249aa9 100644 --- a/extensions/nextcloud-talk/src/inbound.ts +++ b/extensions/nextcloud-talk/src/inbound.ts @@ -122,7 +122,7 @@ export async function handleNextcloudTalkInbound(params: { configGroupAllowFrom.length > 0 ? configGroupAllowFrom : configAllowFrom; const effectiveAllowFrom = [...configAllowFrom, ...storeAllowList].filter(Boolean); - const effectiveGroupAllowFrom = [...baseGroupAllowFrom, ...storeAllowList].filter(Boolean); + const effectiveGroupAllowFrom = [...baseGroupAllowFrom].filter(Boolean); const allowTextCommands = core.channel.commands.shouldHandleTextCommands({ cfg: config as OpenClawConfig,