fix(telegram): guard malformed native menu specs

This commit is contained in:
liuxiaopai-ai
2026-03-03 01:52:31 +08:00
committed by Peter Steinberger
parent ed55b63684
commit 0958d11478
3 changed files with 29 additions and 5 deletions

View File

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

View File

@@ -60,6 +60,27 @@ describe("bot-native-command-menu", () => {
expect(result.issues).toEqual([]); 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<typeof buildPluginTelegramMenuCommands>[0]["specs"];
const result = buildPluginTelegramMenuCommands({
specs: malformedSpecs,
existingCommands: new Set<string>(),
});
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 "/<unknown>" is invalid for Telegram (use a-z, 0-9, underscore; max 32 chars).',
);
});
it("deletes stale commands before setting new menu", async () => { it("deletes stale commands before setting new menu", async () => {
const callOrder: string[] = []; const callOrder: string[] = [];
const deleteMyCommands = vi.fn(async () => { const deleteMyCommands = vi.fn(async () => {

View File

@@ -15,8 +15,8 @@ export type TelegramMenuCommand = {
}; };
type TelegramPluginCommandSpec = { type TelegramPluginCommandSpec = {
name: string; name: unknown;
description: string; description: unknown;
}; };
function isBotCommandsTooMuchError(err: unknown): boolean { function isBotCommandsTooMuchError(err: unknown): boolean {
@@ -54,14 +54,16 @@ export function buildPluginTelegramMenuCommands(params: {
const pluginCommandNames = new Set<string>(); const pluginCommandNames = new Set<string>();
for (const spec of specs) { 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)) { if (!normalized || !TELEGRAM_COMMAND_NAME_PATTERN.test(normalized)) {
const invalidName = rawName.trim() ? rawName : "<unknown>";
issues.push( 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; continue;
} }
const description = spec.description.trim(); const description = typeof spec.description === "string" ? spec.description.trim() : "";
if (!description) { if (!description) {
issues.push(`Plugin command "/${normalized}" is missing a description.`); issues.push(`Plugin command "/${normalized}" is missing a description.`);
continue; continue;