From 43fa40a35dc3237bc78a3e51ed20299ef76e0a46 Mon Sep 17 00:00:00 2001 From: pashpashpash Date: Tue, 28 Apr 2026 16:34:46 -0700 Subject: [PATCH] fix(telegram): use owners for exec approvals (#73852) --- CHANGELOG.md | 1 + docs/channels/telegram.md | 4 +++- docs/tools/exec-approvals-advanced.md | 6 +++--- extensions/telegram/src/approval-native.test.ts | 8 ++++---- extensions/telegram/src/approval-native.ts | 2 +- extensions/telegram/src/config-ui-hints.ts | 2 +- extensions/telegram/src/exec-approvals.test.ts | 16 +++++++++++----- extensions/telegram/src/exec-approvals.ts | 5 +---- .../bundled-channel-config-metadata.generated.ts | 2 +- src/config/types.telegram.ts | 2 +- 10 files changed, 27 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 96c5d4d0630..86e49556815 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai ### Fixes +- Telegram/exec approvals: stop treating general Telegram chat allowlists and `defaultTo` routes as native exec approvers; Telegram now uses explicit `execApprovals.approvers` or owner identity from `commands.ownerAllowFrom`, matching the first-pairing owner bootstrap path. Thanks @pashpashpash. - Plugin SDK/Discord: restore a deprecated `openclaw/plugin-sdk/discord` compatibility facade and the legacy compat group-policy warning export for the published `@openclaw/discord@2026.3.13` package, covering its config, account, directory, status, and thread-binding imports while keeping new plugins on generic SDK subpaths. Fixes #73685; supersedes #73703. Thanks @rderickson9 and @SymbolStar. - Channels/Discord: suppress duplicate gateway monitors when multiple enabled accounts resolve to the same bot token, preferring config tokens over default env fallback and reporting skipped duplicates as disabled. Supersedes #73608. Thanks @kagura-agent. - Control UI/Talk: decode Google Live binary WebSocket JSON frames and stop queued browser audio on interruption or shutdown, so browser Talk leaves `Connecting Talk...` and barge-in no longer plays stale audio. Fixes #73601 and #73460; supersedes #73466. Thanks @Spolen23 and @WadydX. diff --git a/docs/channels/telegram.md b/docs/channels/telegram.md index edd9c3f962a..7996999a780 100644 --- a/docs/channels/telegram.md +++ b/docs/channels/telegram.md @@ -778,10 +778,12 @@ openclaw message poll --channel telegram --target -1001234567890:topic:42 \ Config path: - `channels.telegram.execApprovals.enabled` (auto-enables when at least one approver is resolvable) - - `channels.telegram.execApprovals.approvers` (falls back to numeric owner IDs from `commands.ownerAllowFrom`, `allowFrom`, or `defaultTo`) + - `channels.telegram.execApprovals.approvers` (falls back to numeric owner IDs from `commands.ownerAllowFrom`) - `channels.telegram.execApprovals.target`: `dm` (default) | `channel` | `both` - `agentFilter`, `sessionFilter` + `channels.telegram.allowFrom`, `groupAllowFrom`, and `defaultTo` control who can talk to the bot and where it sends normal replies. They do not make someone an exec approver. The first approved DM pairing bootstraps `commands.ownerAllowFrom` when no command owner exists yet, so the one-owner setup still works without duplicating IDs under `execApprovals.approvers`. + Channel delivery shows the command text in the chat; only enable `channel` or `both` in trusted groups/topics. When the prompt lands in a forum topic, OpenClaw preserves the topic for the approval prompt and the follow-up. Exec approvals expire after 30 minutes by default. Inline approval buttons also require `channels.telegram.capabilities.inlineButtons` to allow the target surface (`dm`, `group`, or `all`). Approval IDs prefixed with `plugin:` resolve through plugin approvals; others resolve through exec approvals first. diff --git a/docs/tools/exec-approvals-advanced.md b/docs/tools/exec-approvals-advanced.md index 14ea8755ec0..e661fb5187a 100644 --- a/docs/tools/exec-approvals-advanced.md +++ b/docs/tools/exec-approvals-advanced.md @@ -271,8 +271,8 @@ Generic model: Native approval clients auto-enable DM-first delivery when all of these are true: - the channel supports native approval delivery -- approvers can be resolved from explicit `execApprovals.approvers` or that - channel's documented fallback sources +- approvers can be resolved from explicit `execApprovals.approvers` or owner + identity such as `commands.ownerAllowFrom` - `channels..execApprovals.enabled` is unset or `"auto"` Set `enabled: false` to disable a native approval client explicitly. Set `enabled: true` to force @@ -295,7 +295,7 @@ Shared behavior: - when a native approval client auto-enables, the default native delivery target is approver DMs - for Discord and Telegram, only resolved approvers can approve or deny - Discord approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom` -- Telegram approvers can be explicit (`execApprovals.approvers`) or inferred from existing owner config (`allowFrom`, plus direct-message `defaultTo` where supported) +- Telegram approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom` - Slack approvers can be explicit (`execApprovals.approvers`) or inferred from `commands.ownerAllowFrom` - Slack native buttons preserve approval id kind, so `plugin:` ids can resolve plugin approvals without a second Slack-local fallback layer diff --git a/extensions/telegram/src/approval-native.test.ts b/extensions/telegram/src/approval-native.test.ts index ad0b95e04c4..d1706a97d94 100644 --- a/extensions/telegram/src/approval-native.test.ts +++ b/extensions/telegram/src/approval-native.test.ts @@ -40,8 +40,8 @@ describe("telegram native approval adapter", () => { expect(text).toContain("`channels.telegram.execApprovals.approvers`"); expect(text).toContain("`commands.ownerAllowFrom`"); - expect(text).toContain("`channels.telegram.allowFrom`"); - expect(text).toContain("`channels.telegram.defaultTo`"); + expect(text).not.toContain("`channels.telegram.allowFrom`"); + expect(text).not.toContain("`channels.telegram.defaultTo`"); expect(text).not.toContain("`channels.telegram.dm.allowFrom`"); }); @@ -54,8 +54,8 @@ describe("telegram native approval adapter", () => { expect(text).toContain("`channels.telegram.accounts.work.execApprovals.approvers`"); expect(text).toContain("`commands.ownerAllowFrom`"); - expect(text).toContain("`channels.telegram.accounts.work.allowFrom`"); - expect(text).toContain("`channels.telegram.accounts.work.defaultTo`"); + expect(text).not.toContain("`channels.telegram.accounts.work.allowFrom`"); + expect(text).not.toContain("`channels.telegram.accounts.work.defaultTo`"); expect(text).not.toContain("`channels.telegram.allowFrom`"); }); diff --git a/extensions/telegram/src/approval-native.ts b/extensions/telegram/src/approval-native.ts index c1c0479600b..4f2781a52ce 100644 --- a/extensions/telegram/src/approval-native.ts +++ b/extensions/telegram/src/approval-native.ts @@ -92,7 +92,7 @@ const telegramNativeApprovalCapability = createApproverRestrictedNativeApprovalC accountId && accountId !== "default" ? `channels.telegram.accounts.${accountId}` : "channels.telegram"; - return `Approve it from the Web UI or terminal UI for now. Telegram supports native exec approvals for this account. Configure \`${prefix}.execApprovals.approvers\`; if you leave it unset, OpenClaw can infer numeric owner IDs from \`commands.ownerAllowFrom\`, \`${prefix}.allowFrom\`, or direct-message \`${prefix}.defaultTo\` when possible. Leave \`${prefix}.execApprovals.enabled\` unset/\`auto\` or set it to \`true\`.`; + return `Approve it from the Web UI or terminal UI for now. Telegram supports native exec approvals for this account. Configure \`${prefix}.execApprovals.approvers\` or \`commands.ownerAllowFrom\`; leave \`${prefix}.execApprovals.enabled\` unset/\`auto\` or set it to \`true\`.`; }, listAccountIds: listTelegramAccountIds, hasApprovers: ({ cfg, accountId }) => diff --git a/extensions/telegram/src/config-ui-hints.ts b/extensions/telegram/src/config-ui-hints.ts index 68300a31ac3..90660c6afbb 100644 --- a/extensions/telegram/src/config-ui-hints.ts +++ b/extensions/telegram/src/config-ui-hints.ts @@ -135,7 +135,7 @@ export const telegramChannelConfigUiHints = { }, "execApprovals.approvers": { label: "Telegram Exec Approval Approvers", - help: "Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom, channels.telegram.allowFrom, and direct-message defaultTo when possible.", + help: "Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible.", }, "execApprovals.agentFilter": { label: "Telegram Exec Approval Agent Filter", diff --git a/extensions/telegram/src/exec-approvals.test.ts b/extensions/telegram/src/exec-approvals.test.ts index c10d078f7b9..b01880e96ed 100644 --- a/extensions/telegram/src/exec-approvals.test.ts +++ b/extensions/telegram/src/exec-approvals.test.ts @@ -121,7 +121,12 @@ describe("telegram exec approvals", () => { isTelegramExecApprovalClientEnabled({ cfg: buildConfig(undefined, { allowFrom: ["123"] }), }), - ).toBe(true); + ).toBe(false); + expect( + isTelegramExecApprovalClientEnabled({ + cfg: buildConfig(undefined, { defaultTo: 123 }), + }), + ).toBe(false); expect( isTelegramExecApprovalClientEnabled({ cfg: buildConfig({ approvers: ["123"] }), @@ -160,7 +165,7 @@ describe("telegram exec approvals", () => { expect(isTelegramExecApprovalApprover({ cfg, senderId: "67890" })).toBe(true); }); - it("infers approvers from allowFrom and direct defaultTo", () => { + it("does not infer approvers from Telegram chat allowlists", () => { const cfg = buildConfig( { enabled: true }, { @@ -169,9 +174,10 @@ describe("telegram exec approvals", () => { }, ); - expect(getTelegramExecApprovalApprovers({ cfg })).toEqual(["12345", "67890"]); - expect(isTelegramExecApprovalApprover({ cfg, senderId: "12345" })).toBe(true); - expect(isTelegramExecApprovalApprover({ cfg, senderId: "67890" })).toBe(true); + expect(getTelegramExecApprovalApprovers({ cfg })).toEqual([]); + expect(isTelegramExecApprovalClientEnabled({ cfg })).toBe(false); + expect(isTelegramExecApprovalApprover({ cfg, senderId: "12345" })).toBe(false); + expect(isTelegramExecApprovalApprover({ cfg, senderId: "67890" })).toBe(false); }); it("defaults target to dm", () => { diff --git a/extensions/telegram/src/exec-approvals.ts b/extensions/telegram/src/exec-approvals.ts index 00c90d63bcb..e8a4df5ee69 100644 --- a/extensions/telegram/src/exec-approvals.ts +++ b/extensions/telegram/src/exec-approvals.ts @@ -58,12 +58,9 @@ export function getTelegramExecApprovalApprovers(params: { cfg: OpenClawConfig; accountId?: string | null; }): string[] { - const account = resolveTelegramAccount(params).config; return resolveApprovalApprovers({ explicit: resolveTelegramExecApprovalConfig(params)?.approvers, - allowFrom: account.allowFrom, - extraAllowFrom: resolveTelegramOwnerApprovers(params.cfg), - defaultTo: account.defaultTo ? String(account.defaultTo) : null, + allowFrom: resolveTelegramOwnerApprovers(params.cfg), normalizeApprover: normalizeTelegramDirectApproverId, }); } diff --git a/src/config/bundled-channel-config-metadata.generated.ts b/src/config/bundled-channel-config-metadata.generated.ts index 3fc59e25f9f..af0dc873ba1 100644 --- a/src/config/bundled-channel-config-metadata.generated.ts +++ b/src/config/bundled-channel-config-metadata.generated.ts @@ -15287,7 +15287,7 @@ export const GENERATED_BUNDLED_CHANNEL_CONFIG_METADATA = [ }, "execApprovals.approvers": { label: "Telegram Exec Approval Approvers", - help: "Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from channels.telegram.allowFrom and direct-message defaultTo when possible.", + help: "Telegram user IDs allowed to approve exec requests for this bot account. Use numeric Telegram user IDs. If you leave this unset, OpenClaw falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible.", }, "execApprovals.agentFilter": { label: "Telegram Exec Approval Agent Filter", diff --git a/src/config/types.telegram.ts b/src/config/types.telegram.ts index 7c6857069e3..cfb9e840800 100644 --- a/src/config/types.telegram.ts +++ b/src/config/types.telegram.ts @@ -67,7 +67,7 @@ export type TelegramExecApprovalTarget = "dm" | "channel" | "both"; export type TelegramExecApprovalConfig = { /** Enable mode for Telegram exec approvals on this account. Default: auto when approvers can be resolved; false disables. */ enabled?: import("./types.approvals.js").NativeExecApprovalEnableMode; - /** Telegram user IDs allowed to approve exec requests. Optional: falls back to numeric owner IDs inferred from allowFrom/defaultTo when possible. */ + /** Telegram user IDs allowed to approve exec requests. Optional: falls back to numeric owner IDs inferred from commands.ownerAllowFrom when possible. */ approvers?: Array; /** Only forward approvals for these agent IDs. Omit = all agents. */ agentFilter?: string[];