From d8695b574dd95e449094a73f9719a3dbd01f5080 Mon Sep 17 00:00:00 2001 From: Yarchik Date: Thu, 2 Jul 2026 01:28:35 +0100 Subject: [PATCH] fix: reject enum member names in @IsEnum for numeric enums TypeScript compiles a numeric enum to a bidirectional map, so Object.keys(entity).map(k => entity[k]) included the reverse-map member names in the accepted set. @IsEnum(NumericEnum) then accepted the member NAMES (e.g. 'Up' for { Up = 1 }) as valid, letting a string through where a number is expected. The default error message already lists only the real values, built from validEnumValues, so the validator accepted values its own message declared invalid. Build the accepted set from validEnumValues, the same helper the decorator already uses for the message. Fixes #1060. --- src/decorator/typechecker/IsEnum.ts | 4 ++-- ...alidation-functions-and-decorators.spec.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/decorator/typechecker/IsEnum.ts b/src/decorator/typechecker/IsEnum.ts index eb3d6b064b..1470da9aa0 100644 --- a/src/decorator/typechecker/IsEnum.ts +++ b/src/decorator/typechecker/IsEnum.ts @@ -7,8 +7,8 @@ export const IS_ENUM = 'isEnum'; * Checks if a given value is the member of the provided enum. */ export function isEnum(value: unknown, entity: any): boolean { - const enumValues = Object.keys(entity).map(k => entity[k]); - return enumValues.includes(value); + const enumValues = validEnumValues(entity); + return enumValues.includes(value as string); } /** diff --git a/test/functional/validation-functions-and-decorators.spec.ts b/test/functional/validation-functions-and-decorators.spec.ts index 4c266f02ee..d6937d6a71 100644 --- a/test/functional/validation-functions-and-decorators.spec.ts +++ b/test/functional/validation-functions-and-decorators.spec.ts @@ -967,6 +967,25 @@ describe('IsEnum', () => { const message = 'someProperty must be one of the following values: first, second'; return checkReturnedError(new MyClassThree(), invalidValues, validationType, message); }); + + it('should not accept the enum member name for a custom indexed enum', () => { + expect(isEnum('First', MyCustomIndexedEnum)).toBeFalsy(); + expect(isEnum('Second', MyCustomIndexedEnum)).toBeFalsy(); + }); + + it('should not accept the enum member name for a default indexed enum', () => { + expect(isEnum('First', MyDefaultIndexedEnum)).toBeFalsy(); + expect(isEnum('Second', MyDefaultIndexedEnum)).toBeFalsy(); + }); + + it('should not accept the reverse-mapped numeric key string', () => { + expect(isEnum('1', MyCustomIndexedEnum)).toBeFalsy(); + expect(isEnum('999', MyCustomIndexedEnum)).toBeFalsy(); + }); + + it('should fail if the enum member name is passed instead of its value', () => { + return checkInvalidValues(new MyClassTwo(), ['First', 'Second']); + }); }); describe('IsDivisibleBy', () => {