Restrict Telegram ops command visibility

---
Build: pass | Tests: pass — 2143 passed (624 files)
This commit is contained in:
Operator & Codex 2026-05-03 21:15:56 +02:00
parent ce8b4c6b8f
commit d383e88b09
3 changed files with 18 additions and 5 deletions

View file

@ -325,7 +325,7 @@ export class TelegramChannel implements Channel {
},
help: async (ctx, chatJid) => {
const admin = isAuthorizedAdmin(ctx);
const ops = Boolean(chatJid && isOpsChatJid(chatJid));
const ops = Boolean(admin && chatJid && isOpsChatJid(chatJid));
await ctx.reply(buildTelegramHelpMessage({ admin, ops }));
},
status: async (ctx, chatJid) => {
@ -938,9 +938,15 @@ export class TelegramChannel implements Channel {
const opsChatId = getOpsChatId();
if (opsChatId !== null) {
await this.bot.api.setMyCommands(getTelegramMenuCommands('ops_chat'), {
scope: { type: 'chat', chat_id: opsChatId },
});
for (const adminChatId of getAdminPrivateChatIds()) {
await this.bot.api.setMyCommands(getTelegramMenuCommands('ops_chat'), {
scope: {
type: 'chat_member',
chat_id: opsChatId,
user_id: adminChatId,
},
});
}
}
} catch (err) {
logger.warn({ err }, 'Failed to publish Telegram command menus');

View file

@ -22,6 +22,13 @@ describe('telegram command registry', () => {
expect(opsHelp).toContain('/publishreport');
});
it('does not expose ops-only help to non-admin users in the ops chat', () => {
const nonAdminOpsHelp = buildTelegramHelpMessage({ admin: false, ops: true });
expect(nonAdminOpsHelp).not.toContain('/newpass');
expect(nonAdminOpsHelp).not.toContain('/publishreport');
expect(nonAdminOpsHelp).toContain('/help');
});
it('keeps general help limited to shared commands', () => {
const generalHelp = buildTelegramHelpMessage({ admin: false, ops: false });
expect(generalHelp).toContain('/help');

View file

@ -247,7 +247,7 @@ export function buildTelegramHelpMessage(opts: {
admin: boolean;
ops: boolean;
}): string {
const scope: TelegramCommandMenuScope = opts.ops
const scope: TelegramCommandMenuScope = opts.admin && opts.ops
? 'ops_chat'
: opts.admin
? 'admin_private'