diff --git a/CHANGELOG.md b/CHANGELOG.md index 7c444e28c04..32fe1aaf3d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -51,6 +51,7 @@ Docs: https://docs.openclaw.ai - Mentions/Slack formatting hardening: add null-safe guards for runtime text normalization paths so malformed/undefined text payloads do not crash mention stripping or mrkdwn conversion. (#31865) Thanks @stone-jin. - Failover/error classification: treat HTTP `529` (provider overloaded, common with Anthropic-compatible APIs) as `rate_limit` so model failover can engage instead of misclassifying the error path. (#31854) Thanks @bugkill3r. - Voice-call/webhook routing: require exact webhook path matches (instead of prefix matches) so lookalike paths cannot reach provider verification/dispatch logic. (#31930) Thanks @afurm. +- Telegram/native command menu hardening: guard plugin command name/description normalization so malformed plugin command specs cannot crash Telegram startup command registration (`trim` on undefined). (#31997) Fixes #31944. Thanks @liuxiaopai-ai. - Web UI/config form: support SecretInput string-or-secret-ref unions in map `additionalProperties`, so provider API key fields stay editable instead of being marked unsupported. (#31866) Thanks @ningding97. - Slack/Bolt startup compatibility: remove invalid `message.channels` and `message.groups` event registrations so Slack providers no longer crash on startup with Bolt 4.6+; channel/group traffic continues through the unified `message` handler (`channel_type`). (#32033) Thanks @mahopan. - Telegram: guard duplicate-token checks and gateway startup token normalization when account tokens are missing, preventing `token.trim()` crashes during status/start flows. (#31973) Thanks @ningding97. diff --git a/src/telegram/bot-native-command-menu.test.ts b/src/telegram/bot-native-command-menu.test.ts index b73d4735875..8077c424437 100644 --- a/src/telegram/bot-native-command-menu.test.ts +++ b/src/telegram/bot-native-command-menu.test.ts @@ -60,6 +60,27 @@ describe("bot-native-command-menu", () => { expect(result.issues).toEqual([]); }); + it("ignores malformed plugin specs without crashing", () => { + const malformedSpecs = [ + { name: "valid", description: " Works " }, + { name: "missing-description", description: undefined }, + { name: undefined, description: "Missing name" }, + ] as unknown as Parameters[0]["specs"]; + + const result = buildPluginTelegramMenuCommands({ + specs: malformedSpecs, + existingCommands: new Set(), + }); + + expect(result.commands).toEqual([{ command: "valid", description: "Works" }]); + expect(result.issues).toContain( + 'Plugin command "/missing_description" is missing a description.', + ); + expect(result.issues).toContain( + 'Plugin command "/" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).', + ); + }); + it("deletes stale commands before setting new menu", async () => { const callOrder: string[] = []; const deleteMyCommands = vi.fn(async () => { diff --git a/src/telegram/bot-native-command-menu.ts b/src/telegram/bot-native-command-menu.ts index 0f993b7cdba..ab41a359c33 100644 --- a/src/telegram/bot-native-command-menu.ts +++ b/src/telegram/bot-native-command-menu.ts @@ -15,8 +15,8 @@ export type TelegramMenuCommand = { }; type TelegramPluginCommandSpec = { - name: string; - description: string; + name: unknown; + description: unknown; }; function isBotCommandsTooMuchError(err: unknown): boolean { @@ -54,14 +54,16 @@ export function buildPluginTelegramMenuCommands(params: { const pluginCommandNames = new Set(); for (const spec of specs) { - const normalized = normalizeTelegramCommandName(spec.name); + const rawName = typeof spec.name === "string" ? spec.name : ""; + const normalized = normalizeTelegramCommandName(rawName); if (!normalized || !TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) { + const invalidName = rawName.trim() ? rawName : ""; issues.push( - `Plugin command "/${spec.name}" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).`, + `Plugin command "/${invalidName}" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).`, ); continue; } - const description = spec.description.trim(); + const description = typeof spec.description === "string" ? spec.description.trim() : ""; if (!description) { issues.push(`Plugin command "/${normalized}" is missing a description.`); continue;