class-validator: fix: "an unknown value was passed to the validate function"

Description

After updating class-validator from 0.13.2 to 0.14.0, we get an error when consuming GraphQL API:

error: "Bad Request"
message: ["an unknown value was passed to the validate function"]
statusCode: 400

Minimal code-snippet showcasing the problem

import {
  Field,
  InputType,
  Int,
  ObjectType,
  ReturnTypeFuncValue,
} from '@nestjs/graphql'
import { IsOptional, IsPositive } from 'class-validator'

@InputType()
export class PaginationInfo {
  @Field(() => String, { nullable: true })
  before: string | undefined

  @Field(() => String, { nullable: true })
  after: string | undefined

  @IsOptional()
  @IsPositive()
  @Field(() => Int, { nullable: true })
  first: number | undefined

  @IsOptional()
  @IsPositive()
  @Field(() => Int, { nullable: true })
  last: number | undefined
}

Expected behavior

We expect to get a response, as with 0.13.2

Actual behavior

An error described above is thrown

About this issue

  • Original URL
  • State: closed
  • Created 2 years ago
  • Reactions: 65
  • Comments: 42 (8 by maintainers)

Commits related to this issue

Most upvoted comments

In my case, my class property has a nested validation. Typing with class transformer @Type decorator resolved.

@ValidateNested() 
@ApiProperty() 
@Type(() => InstanceConfig) // here 
instances: Array<InstanceConfig>;

seems updating @nestjs/common to 9.3.9 will fix the problem

+1

In NestJs, I got the same error but I was sending correct payload, without Unkown values, that makes no sense.

Why the value of forbidUnknownValues changed between 0.13 and 0.14 that caused a breaking change for everyone is in a minor version and not in a major?

We have several microservices using NestJS and this was happening to us for one particular endpoint. In our scenario, we had a property on our DTO that had no validation decorators on it. So for example:

export class MyDto {

myProperty: string;

}

became:

export class MyDto {

@IsString()
myProperty: string;

}

Which resolved the issue. Hope that helps

as you’re using nestjs, in your main.ts you can update your ValidationPipe creation as follow : app.useGlobalPipes(new ValidationPipe({ forbidUnknownValues: false }))

please add a 👍 in the PR description instead of spamming with +1 comments 😃

I agree with @marlon-chagas - even with correct payload I had to disable forbidUnknownValues - something wrong here I think…

Yes. It is total mess, and solutions are available only for NestJS.

Downgrade to:

    "class-validator": "^0.13.2",

fix problem.

The Changelog mentions the forbidUnknownValues Parameter to has a new default: true. (https://github.com/typestack/class-validator/blob/develop/CHANGELOG.md#breaking-changes). So you’ll have to set it to false manually in your respective options.

Edit; Please read the comments below. This could lead to a vulnerability.

Hello guys, i found i guess the solution, in my DTO after add a class-validator this work very well.

wihout the validator in the DTO fields, i receve the error.

import { IsEmail, IsNotEmpty, MaxLength, MinLength } from 'class-validator';

export class CredentialsDto {
  @IsNotEmpty({
    message: 'Informe um endereço de email',
  })
  @IsEmail(
    {},
    {
      message: 'Informe um endereço de email válido',
    },
  )
  @MaxLength(200, {
    message: 'O endereço de email deve ter menos de 200 caracteres',
  })
  email: string;

  @MinLength(6, {
    message: 'A senha deve ter no mínimo 6 caracteres',
  })
  password: string;
}

what about non-NestJs users, it still makes no sense to set forbidUnknownValues: false, is there a solution for that? in the 0.13.x we could send context in the validationOptions at least can we have this back?

Hey @braaar,

Analyzing this issue in my application and it happens that when an endpoint receives a payload using the @Body decorator (nestjs), the output for param.constructor.name is Object for the object that should be validated and the validation fails. If I instantiate the class it using its constructor, the validation works normally (the output for ``param.constructor.nameisMyObject` for example).

Do you think turning forbidUnknownValues false would enable any vulnerabilities?

Thanks!

Nice find!

I’m no security expert, but the implication of allowing unknown values is that attackers could attach some sneaky extra stuff into an otherwise valid payload. Depending on the architecture of your application this could potentially allow them to give themselves heightened access or just simply corrupt the integrity of your database. Document databases like MongoDB and Firestore will typically allow you to save anything in there since there’s no strict schema to adhere to.

const errors = await validate(input);
if(errors.length == 0) {
  await saveUserToDatabase(input) 
}

Of course, it’s entirely possible that your application discards any extra fields from the input and I don’t see a security issue in that case:, but in a way it’s a bit scary to have to rely on future developers to remember this quirk when designing the code and it is arguably quite tedious:

const errors = await validate(input);
if(errors.length == 0) {
  await saveUserToDatabase({
    name: input.name,
    role: input.role
  }) 
}

Why the value of forbidUnknownValues changed between 0.13 and 0.14 that caused a breaking change for everyone is in a minor version and not in a major?

See Semantic Versioning’s specification for version zero:

Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.

On top of that I believe there is an unwritten convention for version zero releases to use minor version changes (0.13 -> 0.14) to reflect breaking changes, and patch versions (0.13.1 -> 0.13.2) for non-breaking changes. This gives users some predictability.

@Hatko – does the issue still persist even if your validation classes are set up to respect forbidUnknownValues?

Have you double checked if your payload contains __typename or some other field that your validation class is not expecting?

I suppose it is possible that production builds is attaching something. Need to check the output.

I was facing this issue too, on NestJS, here is what was wrong First of all for easier debugging use this app.useGlobalPipes( new ValidationPipe({ exceptionFactory: (e) => console.log(e), whitelist: true, transform: true, forbidUnknownValues: true, validateCustomDecorators: true, disableErrorMessages: isProduction, forbidNonWhitelisted: true, transformOptions: { enableImplicitConversion: true, }, }), ); And I would attach a debugger to figure out more info about the issue.

My issue was using a custom MongoDB ID pipe to transform params, here is the implementation

` import { PipeTransform, Injectable, ArgumentMetadata, HttpException, HttpStatus, } from ‘@nestjs/common’; import { Types } from ‘mongoose’;

@Injectable() export class MongoDBIDPipe implements PipeTransform { transform(mongoDBID: string, _: ArgumentMetadata) { if (!Types.ObjectId.isValid(mongoDBID)) { throw new HttpException( validation.invalidMongoDBID, HttpStatus.NOT_ACCEPTABLE, ); } return mongoDBID; } }

`

this returns a string because that’s how it arrives at the controller handler, but I wrote the type as Types.ObjectId, like this

` @UserID(new ValidationPipe({ validateCustomDecorators: true })) userID: string,

@Query() filterQueryDto: FilterQueryDto,

@Param('messagesContainerID', new MongoDBIDPipe())
messagesContainerID: Types.ObjectId,

` and that was causing the error to be thrown but I still didn’t figure out how it relates to the DTO

Similarly, if you are doing some mapping from the input types onto another type after validation, you will have to be careful never to use the spread operator:

// vulnerable
const user = {
  name: input.name, 
  type: input.hasSubscription ? 'subscriber' : 'regular', 
  ...input // this could contain anything!
}
await saveUserToDatabase(user);

// safe
const user = {
  name: input.name, 
  type: input.hasSubscription ? 'subscriber' : 'regular', 
  role: input.role
}
await saveUserToDatabase(user);

I think the preferable option is to forbid unknown values since you will know that the shape of the object is what you think it is after validation.

@israelboudoux

any idea when a patched version will be released?

I have still not seen a reproduction of a supposed bug with forbidUnknownValues, and the gripes people have in this thread are largely caused by a lack of understanding of semantic versioning of major version 0, as I explained previously in this issue.

Until a bug can be reproduced, there is nothing to patch. If no reproduction is brought forth I am considering closing this issue, as letting it stay up is perpetuating a misconception (that the breaking change is a bug).

It doesn’t make sense to close the issue when there are plenty of people having this issue.

I understand the semantic version but I didn’t know that increasing a version would break something that was working correctly. It seems you’re stating that this breaking change was on purpose, if so, could you please point out which one is it and how to address it?

I’ll try to create a sample of an app emulating this issue and I’ll share it later here.

Thanks!

I’m having the same error when using version 0.14.0, but not when moving to 0.13.2, however this version has a critical severity vulnerability, any idea when a patched version will be released?

In my case I’m using NestJS with class-validator and when the backend tries to validate the object an error with the message an unknown value was passed to the validate function is being raised.

Thanks!

@eneilson thank you for pointing the way to dig. Your suggestion fixes the issue and seems like it’s because by default class-validator doesn’t transform nested objects to class instances.

In my case, the fix was a bit more complicated because I have a field containing a JSON string that I parse to an object using another decorator based on Transform from class-transformer. Adding the Type decorator did nothing because it seems to conflict with Transform (what it actually uses under the hood).

The simplest way to debug if the nested objects were parsed correctly is to log the parent DTO to the console and examine the value of the nested field. If it’s just a plain object then class-validator will throw the “unknown value” error. If there’s something like foo: FooClass { foo: 'bar' } then everything should work as expected.