diff --git a/src/telegram/bot-message-context.ts b/src/telegram/bot-message-context.ts index 3ea805c944d..c3a2cfcdcb1 100644 --- a/src/telegram/bot-message-context.ts +++ b/src/telegram/bot-message-context.ts @@ -36,7 +36,12 @@ import { recordChannelActivity } from "../infra/channel-activity.js"; import { resolveAgentRoute } from "../routing/resolve-route.js"; import { resolveThreadSessionKeys } from "../routing/session-key.js"; import { withTelegramApiErrorLogging } from "./api-logging.js"; -import { firstDefined, isSenderAllowed, normalizeAllowFromWithStore } from "./bot-access.js"; +import { + firstDefined, + isSenderAllowed, + normalizeAllowFrom, + normalizeAllowFromWithStore, +} from "./bot-access.js"; import { buildGroupLabel, buildSenderLabel, @@ -189,11 +194,8 @@ export const buildTelegramMessageContext = async ({ const mentionRegexes = buildMentionRegexes(cfg, route.agentId); const effectiveDmAllow = normalizeAllowFromWithStore({ allowFrom, storeAllowFrom, dmPolicy }); const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom); - const effectiveGroupAllow = normalizeAllowFromWithStore({ - allowFrom: groupAllowOverride ?? groupAllowFrom, - storeAllowFrom, - dmPolicy, - }); + // Group sender checks are explicit and must not inherit DM pairing-store entries. + const effectiveGroupAllow = normalizeAllowFrom(groupAllowOverride ?? groupAllowFrom); const hasGroupAllowOverride = typeof groupAllowOverride !== "undefined"; const senderId = msg.from?.id ? String(msg.from.id) : ""; const senderUsername = msg.from?.username ?? ""; diff --git a/src/telegram/bot.create-telegram-bot.test.ts b/src/telegram/bot.create-telegram-bot.test.ts index 942a1c6c2b3..4be6b0dcbf3 100644 --- a/src/telegram/bot.create-telegram-bot.test.ts +++ b/src/telegram/bot.create-telegram-bot.test.ts @@ -1416,6 +1416,30 @@ describe("createTelegramBot", () => { expect(replySpy.mock.calls.length, testCase.name).toBe(0); } }); + it("blocks group sender not in groupAllowFrom even when sender is paired in DM store", async () => { + resetHarnessSpies(); + loadConfig.mockReturnValue({ + channels: { + telegram: { + groupPolicy: "allowlist", + groupAllowFrom: ["222222222"], + groups: { "*": { requireMention: false } }, + }, + }, + }); + readChannelAllowFromStore.mockResolvedValueOnce(["123456789"]); + + await dispatchMessage({ + message: { + chat: { id: -100123456789, type: "group", title: "Test Group" }, + from: { id: 123456789, username: "testuser" }, + text: "hello", + date: 1736380800, + }, + }); + + expect(replySpy).not.toHaveBeenCalled(); + }); it("allows control commands with TG-prefixed groupAllowFrom entries", async () => { loadConfig.mockReturnValue({ channels: { diff --git a/src/telegram/bot/helpers.ts b/src/telegram/bot/helpers.ts index 493ad010082..0e41a7d0b28 100644 --- a/src/telegram/bot/helpers.ts +++ b/src/telegram/bot/helpers.ts @@ -3,11 +3,7 @@ import { formatLocationText, type NormalizedLocation } from "../../channels/loca import { resolveTelegramPreviewStreamMode } from "../../config/discord-preview-streaming.js"; import type { TelegramGroupConfig, TelegramTopicConfig } from "../../config/types.js"; import { readChannelAllowFromStore } from "../../pairing/pairing-store.js"; -import { - firstDefined, - normalizeAllowFromWithStore, - type NormalizedAllowFrom, -} from "../bot-access.js"; +import { firstDefined, normalizeAllowFrom, type NormalizedAllowFrom } from "../bot-access.js"; import type { TelegramStreamMode } from "./types.js"; const TELEGRAM_GENERAL_TOPIC_ID = 1; @@ -51,11 +47,9 @@ export async function resolveTelegramGroupAllowFromContext(params: { resolvedThreadId, ); const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom); - const effectiveGroupAllow = normalizeAllowFromWithStore({ - allowFrom: groupAllowOverride ?? params.groupAllowFrom, - storeAllowFrom, - dmPolicy: params.dmPolicy, - }); + // Group sender access must remain explicit (groupAllowFrom/per-group allowFrom only). + // DM pairing store entries are not a group authorization source. + const effectiveGroupAllow = normalizeAllowFrom(groupAllowOverride ?? params.groupAllowFrom); const hasGroupAllowOverride = typeof groupAllowOverride !== "undefined"; return { resolvedThreadId,