mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-21 21:56:46 +00:00
fix(telegram): honor access group allowlists
This commit is contained in:
@@ -126,6 +126,7 @@ Docs: https://docs.openclaw.ai
|
||||
|
||||
- CLI/completion: guard the shell-profile source line written by `openclaw completion --install` with a file existence check (`[ -f ... ] && source ...` for bash/zsh, `test -f ...; and source ...` for fish) so uninstalling OpenClaw no longer makes new login shells error on a missing completion cache. (#78659) Thanks @sjf.
|
||||
- Cron/doctor: repair persisted cron jobs whose `payload.model` was stored as `"default"`, `"null"`, blank, or JSON `null` by removing the bad override during `openclaw doctor --fix` while keeping cron runtime model validation strict. Fixes #78549. Thanks @bizzle12368239.
|
||||
- Telegram: honor `accessGroup:*` sender allowlists for DMs, groups, native commands, and callback authorization before applying Telegram's numeric sender-ID checks. Fixes #78660. Thanks @manugc.
|
||||
- Doctor/OpenAI Codex: revert the 2026.5.5 `doctor --fix` repair that rewrote valid `openai-codex/*` ChatGPT/Codex OAuth routes to `openai/*`, which could break OAuth-only GPT-5.5 setups or accidentally move users onto the OpenAI API-key route. If 2026.5.5 already changed your default model, run `openclaw models set openai-codex/gpt-5.5 && openclaw config validate` to switch the default agent back to the Codex OAuth PI route. Fixes #78407.
|
||||
- Discord/groups: instruct group-chat agents to stay silent when a message is addressed to someone else, replying only when invited or correcting key facts. (#78615)
|
||||
- Discord/groups: tell Discord-channel agents to wrap bare URLs as `<https://example.com>` so link previews do not expand into uninvited embeds. (#78614)
|
||||
|
||||
@@ -4,6 +4,11 @@ import {
|
||||
mergeDmAllowFromSources,
|
||||
type AllowlistMatch,
|
||||
} from "openclaw/plugin-sdk/allow-from";
|
||||
import {
|
||||
parseAccessGroupAllowFromEntry,
|
||||
resolveAccessGroupAllowFromMatches,
|
||||
} from "openclaw/plugin-sdk/command-auth";
|
||||
import type { OpenClawConfig } from "openclaw/plugin-sdk/config-types";
|
||||
import { createSubsystemLogger } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
|
||||
@@ -76,6 +81,39 @@ export const isSenderAllowed = (params: {
|
||||
return isSenderIdAllowed(allow, senderId, true);
|
||||
};
|
||||
|
||||
export async function expandTelegramAllowFromWithAccessGroups(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
allowFrom?: Array<string | number>;
|
||||
accountId?: string;
|
||||
senderId?: string;
|
||||
}): Promise<string[]> {
|
||||
const allowFrom = (params.allowFrom ?? []).map(String);
|
||||
if (!params.senderId) {
|
||||
return allowFrom;
|
||||
}
|
||||
const matched = await resolveAccessGroupAllowFromMatches({
|
||||
cfg: params.cfg,
|
||||
allowFrom,
|
||||
channel: "telegram",
|
||||
accountId: params.accountId ?? "default",
|
||||
senderId: params.senderId,
|
||||
isSenderAllowed: (senderId, entries) =>
|
||||
isSenderAllowed({
|
||||
allow: normalizeAllowFrom(entries),
|
||||
senderId,
|
||||
}),
|
||||
});
|
||||
if (matched.length === 0) {
|
||||
return allowFrom;
|
||||
}
|
||||
const matchedGroups = new Set(matched);
|
||||
const expanded = allowFrom.filter((entry) => {
|
||||
const groupName = parseAccessGroupAllowFromEntry(entry);
|
||||
return groupName == null || !matchedGroups.has(`accessGroup:${groupName}`);
|
||||
});
|
||||
return Array.from(new Set([...expanded, params.senderId]));
|
||||
}
|
||||
|
||||
export { firstDefined };
|
||||
|
||||
export const resolveSenderAllowMatch = (params: {
|
||||
|
||||
@@ -37,6 +37,7 @@ import {
|
||||
import { resolveTelegramAccount, resolveTelegramMediaRuntimeOptions } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
isSenderAllowed,
|
||||
normalizeDmAllowFromWithStore,
|
||||
type NormalizedAllowFrom,
|
||||
@@ -709,14 +710,17 @@ export const registerTelegramHandlers = ({
|
||||
chatId: number;
|
||||
isGroup: boolean;
|
||||
isForum: boolean;
|
||||
senderId?: string;
|
||||
messageThreadId?: number;
|
||||
groupAllowContext?: TelegramGroupAllowContext;
|
||||
}): Promise<TelegramEventAuthorizationContext> => {
|
||||
const groupAllowContext =
|
||||
params.groupAllowContext ??
|
||||
(await resolveTelegramGroupAllowFromContext({
|
||||
cfg,
|
||||
chatId: params.chatId,
|
||||
accountId,
|
||||
senderId: params.senderId,
|
||||
isGroup: params.isGroup,
|
||||
isForum: params.isForum,
|
||||
messageThreadId: params.messageThreadId,
|
||||
@@ -917,6 +921,7 @@ export const registerTelegramHandlers = ({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
senderId,
|
||||
});
|
||||
const senderAuthorization = authorizeTelegramEventSender({
|
||||
chatId,
|
||||
@@ -1346,10 +1351,13 @@ export const registerTelegramHandlers = ({
|
||||
isForum: callbackMessage.chat.is_forum,
|
||||
getChat,
|
||||
});
|
||||
const senderId = callback.from?.id ? String(callback.from.id) : "";
|
||||
const senderUsername = callback.from?.username ?? "";
|
||||
const eventAuthContext = await resolveTelegramEventAuthorizationContext({
|
||||
chatId,
|
||||
isGroup,
|
||||
isForum,
|
||||
senderId,
|
||||
messageThreadId,
|
||||
});
|
||||
const { resolvedThreadId, dmThreadId, storeAllowFrom, groupConfig } = eventAuthContext;
|
||||
@@ -1360,8 +1368,6 @@ export const registerTelegramHandlers = ({
|
||||
);
|
||||
return;
|
||||
}
|
||||
const senderId = callback.from?.id ? String(callback.from.id) : "";
|
||||
const senderUsername = callback.from?.username ?? "";
|
||||
const authorizationMode: TelegramEventAuthorizationMode =
|
||||
!isGroup || (!execApprovalButtonsEnabled && inlineButtonsScope === "allowlist")
|
||||
? "callback-allowlist"
|
||||
@@ -1888,6 +1894,7 @@ export const registerTelegramHandlers = ({
|
||||
chatId: event.chatId,
|
||||
isGroup: event.isGroup,
|
||||
isForum: event.isForum,
|
||||
senderId: event.senderId,
|
||||
messageThreadId: event.messageThreadId,
|
||||
});
|
||||
const {
|
||||
@@ -1903,8 +1910,14 @@ export const registerTelegramHandlers = ({
|
||||
} = eventAuthContext;
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
const expandedDmAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
accountId,
|
||||
senderId: event.senderId,
|
||||
});
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy,
|
||||
});
|
||||
|
||||
@@ -10,7 +10,12 @@ import { normalizeAccountId, resolveThreadSessionKeys } from "openclaw/plugin-sd
|
||||
import { logVerbose } from "openclaw/plugin-sdk/runtime-env";
|
||||
import { mergeTelegramAccountConfig, resolveDefaultTelegramAccountId } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { firstDefined, normalizeAllowFrom, normalizeDmAllowFromWithStore } from "./bot-access.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
firstDefined,
|
||||
normalizeAllowFrom,
|
||||
normalizeDmAllowFromWithStore,
|
||||
} from "./bot-access.js";
|
||||
import { resolveTelegramInboundBody } from "./bot-message-context.body.js";
|
||||
import {
|
||||
buildTelegramInboundContextPayload,
|
||||
@@ -258,13 +263,25 @@ export const buildTelegramMessageContext = async ({
|
||||
const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom);
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
const expandedDmAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg: freshCfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
accountId: account.accountId,
|
||||
senderId,
|
||||
});
|
||||
const effectiveDmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
});
|
||||
// Group sender checks are explicit and must not inherit DM pairing-store entries.
|
||||
const effectiveGroupAllow = normalizeAllowFrom(groupAllowOverride ?? groupAllowFrom);
|
||||
const expandedGroupAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg: freshCfg,
|
||||
allowFrom: groupAllowOverride ?? groupAllowFrom,
|
||||
accountId: account.accountId,
|
||||
senderId,
|
||||
});
|
||||
const effectiveGroupAllow = normalizeAllowFrom(expandedGroupAllowFrom);
|
||||
const hasGroupAllowOverride = groupAllowOverride !== undefined;
|
||||
const senderUsername = msg.from?.username ?? "";
|
||||
const baseAccess = evaluateTelegramGroupBaseAccess({
|
||||
|
||||
@@ -53,7 +53,11 @@ import {
|
||||
} from "openclaw/plugin-sdk/text-runtime";
|
||||
import { resolveTelegramAccount } from "./accounts.js";
|
||||
import { withTelegramApiErrorLogging } from "./api-logging.js";
|
||||
import { isSenderAllowed, normalizeDmAllowFromWithStore } from "./bot-access.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
isSenderAllowed,
|
||||
normalizeDmAllowFromWithStore,
|
||||
} from "./bot-access.js";
|
||||
import type { TelegramBotDeps } from "./bot-deps.js";
|
||||
import type { TelegramMediaRef } from "./bot-message-context.js";
|
||||
import type { TelegramMessageContextOptions } from "./bot-message-context.types.js";
|
||||
@@ -489,9 +493,13 @@ async function resolveTelegramCommandAuth(params: {
|
||||
} = params;
|
||||
const { chatId, isGroup, isForum, messageThreadId, threadParams } =
|
||||
await resolveTelegramNativeCommandThreadContext({ msg, bot });
|
||||
const senderId = msg.from?.id ? String(msg.from.id) : "";
|
||||
const senderUsername = msg.from?.username ?? "";
|
||||
const groupAllowContext = await resolveTelegramGroupAllowFromContext({
|
||||
cfg,
|
||||
chatId,
|
||||
accountId,
|
||||
senderId,
|
||||
isGroup,
|
||||
isForum,
|
||||
messageThreadId,
|
||||
@@ -522,8 +530,12 @@ async function resolveTelegramCommandAuth(params: {
|
||||
}
|
||||
// For DMs, prefer per-DM/topic allowFrom (groupAllowOverride) over account-level allowFrom
|
||||
const dmAllowFrom = groupAllowOverride ?? allowFrom;
|
||||
const senderId = msg.from?.id ? String(msg.from.id) : "";
|
||||
const senderUsername = msg.from?.username ?? "";
|
||||
const expandedDmAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg,
|
||||
allowFrom: dmAllowFrom,
|
||||
accountId,
|
||||
senderId,
|
||||
});
|
||||
const commandsAllowFrom = cfg.commands?.allowFrom;
|
||||
const commandsAllowFromConfigured =
|
||||
commandsAllowFrom != null &&
|
||||
@@ -627,7 +639,7 @@ async function resolveTelegramCommandAuth(params: {
|
||||
}
|
||||
|
||||
const dmAllow = normalizeDmAllowFromWithStore({
|
||||
allowFrom: dmAllowFrom,
|
||||
allowFrom: expandedDmAllowFrom,
|
||||
storeAllowFrom: isGroup ? [] : storeAllowFrom,
|
||||
dmPolicy: effectiveDmPolicy,
|
||||
});
|
||||
|
||||
@@ -1580,6 +1580,60 @@ describe("createTelegramBot", () => {
|
||||
},
|
||||
expectedReplyCount: 1,
|
||||
},
|
||||
{
|
||||
name: "allows group messages from senders in accessGroup allowFrom when groupPolicy is 'allowlist'",
|
||||
config: {
|
||||
accessGroups: {
|
||||
owners: {
|
||||
type: "message.senders",
|
||||
members: {
|
||||
telegram: ["123456789"],
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groupPolicy: "allowlist",
|
||||
allowFrom: ["accessGroup:owners"],
|
||||
groups: { "*": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
chat: { id: -100123456789, type: "group", title: "Test Group" },
|
||||
from: { id: 123456789, username: "testuser" },
|
||||
text: "hello",
|
||||
date: 1736380800,
|
||||
},
|
||||
expectedReplyCount: 1,
|
||||
},
|
||||
{
|
||||
name: "blocks group messages from senders outside accessGroup allowFrom when groupPolicy is 'allowlist'",
|
||||
config: {
|
||||
accessGroups: {
|
||||
owners: {
|
||||
type: "message.senders",
|
||||
members: {
|
||||
telegram: ["123456789"],
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
groupPolicy: "allowlist",
|
||||
allowFrom: ["accessGroup:owners"],
|
||||
groups: { "*": { requireMention: false } },
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
chat: { id: -100123456789, type: "group", title: "Test Group" },
|
||||
from: { id: 999999, username: "notallowed" },
|
||||
text: "hello",
|
||||
date: 1736380800,
|
||||
},
|
||||
expectedReplyCount: 0,
|
||||
},
|
||||
{
|
||||
name: "blocks group messages when allowFrom is configured with @username entries (numeric IDs required)",
|
||||
config: {
|
||||
@@ -2649,6 +2703,31 @@ describe("createTelegramBot", () => {
|
||||
},
|
||||
expectedReplyCount: 1,
|
||||
},
|
||||
{
|
||||
name: "allows direct messages with accessGroup allowFrom entries",
|
||||
config: {
|
||||
accessGroups: {
|
||||
owners: {
|
||||
type: "message.senders",
|
||||
members: {
|
||||
telegram: ["123456789"],
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
telegram: {
|
||||
allowFrom: ["accessGroup:owners"],
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
chat: { id: 777777777, type: "private" },
|
||||
from: { id: 123456789, username: "testuser" },
|
||||
text: "hello",
|
||||
date: 1736380800,
|
||||
},
|
||||
expectedReplyCount: 1,
|
||||
},
|
||||
{
|
||||
name: "falls back to direct message chat id when sender user id is missing",
|
||||
config: {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import type { Chat, Message } from "@grammyjs/types";
|
||||
import { formatLocationText } from "openclaw/plugin-sdk/channel-inbound";
|
||||
import type {
|
||||
OpenClawConfig,
|
||||
TelegramAccountConfig,
|
||||
TelegramDirectConfig,
|
||||
TelegramGroupConfig,
|
||||
@@ -10,7 +11,12 @@ import type {
|
||||
import { readChannelAllowFromStore } from "openclaw/plugin-sdk/conversation-runtime";
|
||||
import { normalizeAccountId } from "openclaw/plugin-sdk/routing";
|
||||
import { normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime";
|
||||
import { firstDefined, normalizeAllowFrom, type NormalizedAllowFrom } from "../bot-access.js";
|
||||
import {
|
||||
expandTelegramAllowFromWithAccessGroups,
|
||||
firstDefined,
|
||||
normalizeAllowFrom,
|
||||
type NormalizedAllowFrom,
|
||||
} from "../bot-access.js";
|
||||
import { normalizeTelegramReplyToMessageId } from "../outbound-params.js";
|
||||
import { resolveTelegramPreviewStreamMode } from "../preview-streaming.js";
|
||||
import {
|
||||
@@ -168,8 +174,10 @@ export function withResolvedTelegramForumFlag<T extends { chat: object }>(
|
||||
}
|
||||
|
||||
export async function resolveTelegramGroupAllowFromContext(params: {
|
||||
cfg?: OpenClawConfig;
|
||||
chatId: string | number;
|
||||
accountId?: string;
|
||||
senderId?: string;
|
||||
isGroup?: boolean;
|
||||
isForum?: boolean;
|
||||
messageThreadId?: number | null;
|
||||
@@ -214,7 +222,13 @@ export async function resolveTelegramGroupAllowFromContext(params: {
|
||||
const groupAllowOverride = firstDefined(topicConfig?.allowFrom, groupConfig?.allowFrom);
|
||||
// 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 expandedGroupAllowFrom = await expandTelegramAllowFromWithAccessGroups({
|
||||
cfg: params.cfg,
|
||||
allowFrom: groupAllowOverride ?? params.groupAllowFrom,
|
||||
accountId,
|
||||
senderId: params.senderId,
|
||||
});
|
||||
const effectiveGroupAllow = normalizeAllowFrom(expandedGroupAllowFrom);
|
||||
const hasGroupAllowOverride = groupAllowOverride !== undefined;
|
||||
return {
|
||||
resolvedThreadId,
|
||||
|
||||
Reference in New Issue
Block a user