From 5f113f0a842d6e6da05e3d9065d99edb40c1fdd3 Mon Sep 17 00:00:00 2001 From: Cody Date: Sat, 11 Apr 2026 20:55:23 -0400 Subject: [PATCH 1/2] fix(discord): sanitize autocomplete choices --- .../src/command/abstract-command.listener.ts | 37 ++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/packages/discord/src/command/abstract-command.listener.ts b/packages/discord/src/command/abstract-command.listener.ts index 64a460791..7e05d5569 100644 --- a/packages/discord/src/command/abstract-command.listener.ts +++ b/packages/discord/src/command/abstract-command.listener.ts @@ -8,6 +8,7 @@ import * as Sentry from "@sentry/node"; import { + type APIApplicationCommandOptionChoice, type APIUser, ApplicationCommandOptionType, GatewayDispatchEvents, @@ -42,6 +43,10 @@ export interface ExecuteCommandOptions { message?: IMessage | Message; } +const MAX_AUTOCOMPLETE_CHOICES = 25; +const MAX_CHOICE_NAME_LENGTH = 100; +const MAX_STRING_CHOICE_VALUE_LENGTH = 100; + export abstract class AbstractCommandListener { protected hooks: Map; protected readonly logger = new Logger("CommandListener"); @@ -262,10 +267,40 @@ export abstract class AbstractCommandListener { return { type: InteractionResponseType.ApplicationCommandAutocompleteResult, - data: { choices: response }, + data: { choices: this.sanitizeAutocompleteChoices(response) }, }; } + private sanitizeAutocompleteChoices( + choices: APIApplicationCommandOptionChoice[] = [] + ): APIApplicationCommandOptionChoice[] { + return choices + .flatMap((choice) => { + const valueName = + choice.value === undefined || choice.value === null ? + "" : + String(choice.value); + const name = (choice.name || valueName).trim(); + + if (!name) return []; + + if ( + typeof choice.value === "string" && + (choice.value.trim().length === 0 || + choice.value.length > MAX_STRING_CHOICE_VALUE_LENGTH) + ) + return []; + + return [ + { + ...choice, + name: name.slice(0, MAX_CHOICE_NAME_LENGTH), + }, + ]; + }) + .slice(0, MAX_AUTOCOMPLETE_CHOICES); + } + private async onInteraction(interaction: Interaction): Promise { if (interaction.isCommandInteraction()) { this.onCommand(interaction); From 0aa1aad77c15ffd77176eb7bed06a9ad44e70a32 Mon Sep 17 00:00:00 2001 From: Cody Date: Fri, 29 May 2026 21:34:03 -0600 Subject: [PATCH 2/2] fix(discord): improve autocomplete choice sanitization * Ensure choices with undefined or null values are excluded * Trim choice names and handle empty strings more effectively * Maintain maximum length constraints for choice values --- .../discord/src/command/abstract-command.listener.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/discord/src/command/abstract-command.listener.ts b/packages/discord/src/command/abstract-command.listener.ts index 7e05d5569..58a0d7eb5 100644 --- a/packages/discord/src/command/abstract-command.listener.ts +++ b/packages/discord/src/command/abstract-command.listener.ts @@ -276,11 +276,10 @@ export abstract class AbstractCommandListener { ): APIApplicationCommandOptionChoice[] { return choices .flatMap((choice) => { - const valueName = - choice.value === undefined || choice.value === null ? - "" : - String(choice.value); - const name = (choice.name || valueName).trim(); + if (choice.value === undefined || choice.value === null) return []; + + const valueName = String(choice.value); + const name = (choice.name ?? valueName).trim(); if (!name) return []; @@ -288,8 +287,9 @@ export abstract class AbstractCommandListener { typeof choice.value === "string" && (choice.value.trim().length === 0 || choice.value.length > MAX_STRING_CHOICE_VALUE_LENGTH) - ) + ) { return []; + } return [ {