fix(security): close Critical IDOR/authz gaps (C-1/C-2)
C-1: messaging endpoints now enforce channel participation (public
channels open; private require channel_members). authorUserId is
forced to the authenticated user (no more author spoofing); edit/
delete require message-author ownership; history read gated too.
C-2: PUT /commands body strictly validated + size-capped via
SyncCommandsDto (kills catalog poisoning / DoS). Optional
FABRIC_BACKEND_GUILD_COMMANDS_SYNC_KEY restricts the write to the
plugin when set; never weaker than before when unset.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
src/commands/dto.sync-commands.dto.ts
Normal file
102
src/commands/dto.sync-commands.dto.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
ArrayMaxSize,
|
||||
IsArray,
|
||||
IsBoolean,
|
||||
IsOptional,
|
||||
IsString,
|
||||
MaxLength,
|
||||
ValidateNested,
|
||||
} from 'class-validator';
|
||||
import { Type } from 'class-transformer';
|
||||
|
||||
// Guild C-2: the slash-command catalog is guild-global and rendered by the
|
||||
// frontend `/` autocomplete. Without a strict schema + caps a single
|
||||
// authenticated caller could poison it or blow up the DB / clients.
|
||||
// The global ValidationPipe runs with { whitelist, forbidNonWhitelisted },
|
||||
// so any unknown field is rejected.
|
||||
|
||||
class CommandChoiceDto {
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
value!: string;
|
||||
|
||||
@IsString()
|
||||
@MaxLength(200)
|
||||
label!: string;
|
||||
}
|
||||
|
||||
class CommandArgDto {
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(40)
|
||||
type?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
required?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
captureRemaining?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
preferAutocomplete?: boolean;
|
||||
|
||||
// null when there are no choices (plugin sends explicit null).
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ArrayMaxSize(100)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandChoiceDto)
|
||||
choices?: CommandChoiceDto[] | null;
|
||||
}
|
||||
|
||||
class CommandSpecDto {
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
name!: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(100)
|
||||
nativeName?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(500)
|
||||
description?: string;
|
||||
|
||||
@IsOptional()
|
||||
@IsBoolean()
|
||||
acceptsArgs?: boolean;
|
||||
|
||||
@IsOptional()
|
||||
@IsArray()
|
||||
@ArrayMaxSize(50)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandArgDto)
|
||||
args?: CommandArgDto[];
|
||||
|
||||
@IsOptional()
|
||||
@IsString()
|
||||
@MaxLength(20)
|
||||
argsParsing?: string;
|
||||
}
|
||||
|
||||
export class SyncCommandsDto {
|
||||
@IsArray()
|
||||
@ArrayMaxSize(200)
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => CommandSpecDto)
|
||||
commands!: CommandSpecDto[];
|
||||
}
|
||||
Reference in New Issue
Block a user