security(telegram): fail closed group allowlist against DM pairing store

This commit is contained in:
bmendonca3
2026-02-24 19:07:20 -07:00
committed by Ayaan Zaidi
parent 5500000492
commit c7352f6b3f
3 changed files with 36 additions and 16 deletions

View File

@@ -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 ?? "";

View File

@@ -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: {

View File

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