class-validator is safe to use in production today, but only if you run version 0.14.0 or later and understand why the old defaults allowed a full validation bypass. The library sits in the request path of nearly every NestJS application and millions of weekly installs, which makes its configuration one of the highest-leverage security decisions in a TypeScript backend. This guide covers the package's history, its one serious CVE, and the settings that separate a real validation layer from decorative decorators.
What class-validator actually does
The library implements decorator-based validation for classes. You annotate DTO properties with constraints like @IsEmail(), @Length(10, 20), or @IsInt(), then call validate() on an instance and get back a list of constraint violations:
import { IsEmail, Length, validate } from 'class-validator';
class CreateUserDto {
@IsEmail()
email: string;
@Length(8, 72)
password: string;
}
const errors = await validate(dto);
if (errors.length > 0) {
// reject the request
}
In NestJS this happens implicitly through ValidationPipe, which is why many teams depend on the class validator behavior without ever reading its docs. That indirection is exactly how the library's most famous vulnerability stayed exploitable in real applications for years.
CVE-2019-18413: the validation bypass that shaped the project
In 2019, researchers reported that validate() in class-validator 0.10.2 could be bypassed entirely (CVE-2019-18413, GHSA-fj58-h2fr-3pp2). If an attacker sent an object whose shape did not match any known validation metadata, the validator had nothing to check and returned zero errors. Unvalidated input then flowed straight into query builders and templates, enabling SQL injection and XSS downstream.
The mitigation existed the whole time: a forbidUnknownValues option that rejects objects with no validation metadata. The problem was that it defaulted to false and was barely documented, so almost everyone ran the vulnerable configuration. The advisory's most important line is not about the bug but about the fix: the default for forbidUnknownValues was changed to true in version 0.14.0.
Practical takeaways:
- If your lockfile resolves class-validator to anything below 0.14.0, upgrade. Do not just set the flag manually and move on; older lines are no longer where fixes land.
- If you upgraded and suddenly see
an unknown value was passed to the validate functionerrors, that is the safe default doing its job — fix the DTO wiring rather than flipping the flag back.
Maintenance health in 2025
The project lives under the TypeStack organization on GitHub and remains one of the most-downloaded validation libraries on npm, at roughly ten million weekly downloads. Releases are not rapid-fire, but the 0.14.x line has continued to receive maintenance, and the security-relevant default change shows the maintainers respond to real-world abuse patterns. The version number still starting with zero after a decade makes some reviewers nervous, but by the criteria that matter — advisory response, download base, active issue triage — the package is in sustainable shape.
Searching for npm class-validator on a scanner or registry page will also surface a long tail of forks and webjars repackagings; stick to the canonical class-validator package so advisories actually reach you.
The NestJS settings that matter
Most production exposure comes through NestJS, so this is where configuration review pays off. A hardened global pipe looks like this:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // strip properties without decorators
forbidNonWhitelisted: true, // reject instead of silently stripping
transform: true, // convert payloads to DTO instances
forbidUnknownValues: true, // default since 0.14.0, keep it explicit
}),
);
Each flag closes a distinct hole:
whitelistprevents mass-assignment, where extra JSON keys likeisAdmin: trueride along into your ORM entities.forbidNonWhitelistedturns silent stripping into a 400 response, which surfaces probing attempts in your logs.transformguarantees you are validating a class instance, not a plain object that never matched metadata.
If you use bare validate() outside NestJS, pass the equivalent options object yourself. Validation groups and conditional decorators (@ValidateIf) deserve extra review because they create code paths where a property is legitimately unvalidated.
Decorators are not sanitizers
A recurring misuse: treating class-validator as an output-encoding layer. @IsString() happily accepts <script>alert(1)</script> because it is, in fact, a string. The library checks shape and constraints; it does not neutralize content. Pair it with parameterized queries for SQL, contextual encoding for HTML, and an allowlist approach (@IsIn, @Matches with anchored patterns) for anything that ends up in a command or path.
Anchoring matters more than people expect. @Matches(/[a-z]+/) passes ../../etc/passwd because the regex only needs to find one lowercase run. Write /^[a-z]+$/ when you mean the whole value.
Keeping the dependency itself honest
class-validator pulls in libphonenumber-js and validator as dependencies, so your exposure is a small graph, not a single package. Pin the version range in your manifest, commit the lockfile, and let a scanner watch the tree: software composition analysis will flag a vulnerable transitive validator release even when your direct dependency looks clean. An SCA tool such as Safeguard can also tell you whether a flagged advisory is reachable from your actual imports, which keeps upgrade work proportional to risk.
For teams building a broader review habit around npm class-validator style utility dependencies, the checklist is short: canonical package name, current major line, lockfile committed, advisory feed monitored, and defaults reviewed once per major upgrade. The Safeguard Academy has deeper material on turning that checklist into CI policy.
Migration notes when upgrading old projects
Projects stuck on 0.13.x usually hesitate because 0.14.0 tightened behavior. Plan for three things:
- Requests that previously "passed" because they matched no metadata will now 400. That is a bug fix, but it can break sloppy internal callers.
- If you construct validation targets manually, ensure they are class instances (
plainToInstancefrom class-transformer), or the unknown-value rejection will trigger. - Re-run your integration tests with malformed payloads — empty objects, arrays where objects are expected, nested garbage. The class validator npm ecosystem has plenty of copy-pasted config from the vulnerable era, and tests are how you find yours.
FAQ
Is class-validator still maintained?
Yes. It is maintained under the TypeStack organization, sits at roughly ten million weekly downloads, and the 0.14.x line continues to receive releases. It is not the fastest-moving project on npm, but it is not abandoned.
What was CVE-2019-18413 and am I affected?
It was a validation bypass: objects with no validation metadata passed validate() untouched, enabling SQL injection and XSS downstream. You are affected if you run a version below 0.14.0 without explicitly setting forbidUnknownValues: true. Upgrading to 0.14.0 or later fixes the default.
Does class-validator sanitize input against XSS?
No. It validates shape and constraints only. Combine it with output encoding, parameterized queries, and strict allowlist patterns for values that reach interpreters.
Should I use whitelist mode in NestJS?
Yes — whitelist: true plus forbidNonWhitelisted: true blocks mass-assignment and makes probing visible. It is the single most valuable configuration change for most NestJS APIs.